json 1.4.6 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of json might be problematic. Click here for more details.
- data/CHANGES +6 -0
- data/COPYING-json-jruby +57 -0
- data/README-json-jruby.markdown +33 -0
- data/Rakefile +224 -119
- data/VERSION +1 -1
- data/benchmarks/generator2_benchmark.rb +1 -1
- data/benchmarks/generator_benchmark.rb +1 -1
- data/ext/json/ext/generator/generator.c +20 -20
- data/ext/json/ext/generator/generator.h +7 -7
- data/ext/json/ext/parser/extconf.rb +1 -0
- data/ext/json/ext/parser/parser.c +122 -88
- data/ext/json/ext/parser/parser.h +7 -0
- data/ext/json/ext/parser/parser.rl +54 -20
- data/java/lib/bytelist-1.0.6.jar +0 -0
- data/java/lib/jcodings.jar +0 -0
- data/java/src/json/ext/ByteListTranscoder.java +167 -0
- data/java/src/json/ext/Generator.java +441 -0
- data/java/src/json/ext/GeneratorMethods.java +231 -0
- data/java/src/json/ext/GeneratorService.java +42 -0
- data/java/src/json/ext/GeneratorState.java +473 -0
- data/java/src/json/ext/OptionsReader.java +119 -0
- data/java/src/json/ext/Parser.java +2295 -0
- data/java/src/json/ext/Parser.rl +825 -0
- data/java/src/json/ext/ParserService.java +34 -0
- data/java/src/json/ext/RuntimeInfo.java +119 -0
- data/java/src/json/ext/StringDecoder.java +166 -0
- data/java/src/json/ext/StringEncoder.java +106 -0
- data/java/src/json/ext/Utils.java +89 -0
- data/json-java.gemspec +20 -0
- data/lib/json/add/core.rb +1 -2
- data/lib/json/add/rails.rb +4 -54
- data/lib/json/common.rb +36 -8
- data/lib/json/editor.rb +1 -3
- data/lib/json/ext.rb +2 -2
- data/lib/json/pure.rb +2 -64
- data/lib/json/pure/generator.rb +10 -8
- data/lib/json/pure/parser.rb +23 -12
- data/lib/json/version.rb +1 -1
- data/tests/setup_variant.rb +11 -0
- data/tests/test_json.rb +1 -5
- data/tests/test_json_addition.rb +14 -9
- data/tests/test_json_encoding.rb +9 -12
- data/tests/test_json_fixtures.rb +9 -8
- data/tests/test_json_generate.rb +3 -5
- data/tests/test_json_string_matching.rb +40 -0
- data/tests/test_json_unicode.rb +1 -5
- metadata +51 -13
- data/tests/test_json_rails.rb +0 -144
@@ -0,0 +1,825 @@
|
|
1
|
+
/*
|
2
|
+
* This code is copyrighted work by Daniel Luz <dev at mernen dot com>.
|
3
|
+
*
|
4
|
+
* Distributed under the Ruby and GPLv2 licenses; see COPYING and GPL files
|
5
|
+
* for details.
|
6
|
+
*/
|
7
|
+
package json.ext;
|
8
|
+
|
9
|
+
import org.jruby.Ruby;
|
10
|
+
import org.jruby.RubyArray;
|
11
|
+
import org.jruby.RubyClass;
|
12
|
+
import org.jruby.RubyEncoding;
|
13
|
+
import org.jruby.RubyFloat;
|
14
|
+
import org.jruby.RubyHash;
|
15
|
+
import org.jruby.RubyInteger;
|
16
|
+
import org.jruby.RubyModule;
|
17
|
+
import org.jruby.RubyNumeric;
|
18
|
+
import org.jruby.RubyObject;
|
19
|
+
import org.jruby.RubyString;
|
20
|
+
import org.jruby.anno.JRubyMethod;
|
21
|
+
import org.jruby.exceptions.JumpException;
|
22
|
+
import org.jruby.exceptions.RaiseException;
|
23
|
+
import org.jruby.runtime.Block;
|
24
|
+
import org.jruby.runtime.ObjectAllocator;
|
25
|
+
import org.jruby.runtime.ThreadContext;
|
26
|
+
import org.jruby.runtime.Visibility;
|
27
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
28
|
+
import org.jruby.util.ByteList;
|
29
|
+
|
30
|
+
/**
|
31
|
+
* The <code>JSON::Ext::Parser</code> class.
|
32
|
+
*
|
33
|
+
* <p>This is the JSON parser implemented as a Java class. To use it as the
|
34
|
+
* standard parser, set
|
35
|
+
* <pre>JSON.parser = JSON::Ext::Parser</pre>
|
36
|
+
* This is performed for you when you <code>include "json/ext"</code>.
|
37
|
+
*
|
38
|
+
* <p>This class does not perform the actual parsing, just acts as an interface
|
39
|
+
* to Ruby code. When the {@link #parse()} method is invoked, a
|
40
|
+
* Parser.ParserSession object is instantiated, which handles the process.
|
41
|
+
*
|
42
|
+
* @author mernen
|
43
|
+
*/
|
44
|
+
public class Parser extends RubyObject {
|
45
|
+
private final RuntimeInfo info;
|
46
|
+
private RubyString vSource;
|
47
|
+
private RubyString createId;
|
48
|
+
private boolean createAdditions;
|
49
|
+
private int maxNesting;
|
50
|
+
private boolean allowNaN;
|
51
|
+
private boolean symbolizeNames;
|
52
|
+
private RubyClass objectClass;
|
53
|
+
private RubyClass arrayClass;
|
54
|
+
private RubyHash match_string;
|
55
|
+
|
56
|
+
private static final int DEFAULT_MAX_NESTING = 19;
|
57
|
+
|
58
|
+
private static final String JSON_MINUS_INFINITY = "-Infinity";
|
59
|
+
// constant names in the JSON module containing those values
|
60
|
+
private static final String CONST_NAN = "NaN";
|
61
|
+
private static final String CONST_INFINITY = "Infinity";
|
62
|
+
private static final String CONST_MINUS_INFINITY = "MinusInfinity";
|
63
|
+
|
64
|
+
static final ObjectAllocator ALLOCATOR = new ObjectAllocator() {
|
65
|
+
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
|
66
|
+
return new Parser(runtime, klazz);
|
67
|
+
}
|
68
|
+
};
|
69
|
+
|
70
|
+
/**
|
71
|
+
* Multiple-value return for internal parser methods.
|
72
|
+
*
|
73
|
+
* <p>All the <code>parse<var>Stuff</var></code> methods return instances of
|
74
|
+
* <code>ParserResult</code> when successful, or <code>null</code> when
|
75
|
+
* there's a problem with the input data.
|
76
|
+
*/
|
77
|
+
static final class ParserResult {
|
78
|
+
/**
|
79
|
+
* The result of the successful parsing. Should never be
|
80
|
+
* <code>null</code>.
|
81
|
+
*/
|
82
|
+
final IRubyObject result;
|
83
|
+
/**
|
84
|
+
* The point where the parser returned.
|
85
|
+
*/
|
86
|
+
final int p;
|
87
|
+
|
88
|
+
ParserResult(IRubyObject result, int p) {
|
89
|
+
this.result = result;
|
90
|
+
this.p = p;
|
91
|
+
}
|
92
|
+
}
|
93
|
+
|
94
|
+
public Parser(Ruby runtime, RubyClass metaClass) {
|
95
|
+
super(runtime, metaClass);
|
96
|
+
info = RuntimeInfo.forRuntime(runtime);
|
97
|
+
}
|
98
|
+
|
99
|
+
/**
|
100
|
+
* <code>Parser.new(source, opts = {})</code>
|
101
|
+
*
|
102
|
+
* <p>Creates a new <code>JSON::Ext::Parser</code> instance for the string
|
103
|
+
* <code>source</code>.
|
104
|
+
* It will be configured by the <code>opts</code> Hash.
|
105
|
+
* <code>opts</code> can have the following keys:
|
106
|
+
*
|
107
|
+
* <dl>
|
108
|
+
* <dt><code>:max_nesting</code>
|
109
|
+
* <dd>The maximum depth of nesting allowed in the parsed data
|
110
|
+
* structures. Disable depth checking with <code>:max_nesting => false|nil|0</code>,
|
111
|
+
* it defaults to 19.
|
112
|
+
*
|
113
|
+
* <dt><code>:allow_nan</code>
|
114
|
+
* <dd>If set to <code>true</code>, allow <code>NaN</code>,
|
115
|
+
* <code>Infinity</code> and <code>-Infinity</code> in defiance of RFC 4627
|
116
|
+
* to be parsed by the Parser. This option defaults to <code>false</code>.
|
117
|
+
*
|
118
|
+
* <dt><code>:symbolize_names</code>
|
119
|
+
* <dd>If set to <code>true</code>, returns symbols for the names (keys) in
|
120
|
+
* a JSON object. Otherwise strings are returned, which is also the default.
|
121
|
+
*
|
122
|
+
* <dt><code>:create_additions</code>
|
123
|
+
* <dd>If set to <code>false</code>, the Parser doesn't create additions
|
124
|
+
* even if a matchin class and <code>create_id</code> was found. This option
|
125
|
+
* defaults to <code>true</code>.
|
126
|
+
*
|
127
|
+
* <dt><code>:object_class</code>
|
128
|
+
* <dd>Defaults to Hash.
|
129
|
+
*
|
130
|
+
* <dt><code>:array_class</code>
|
131
|
+
* <dd>Defaults to Array.
|
132
|
+
* </dl>
|
133
|
+
*/
|
134
|
+
@JRubyMethod(name = "new", required = 1, optional = 1, meta = true)
|
135
|
+
public static IRubyObject newInstance(IRubyObject clazz, IRubyObject[] args, Block block) {
|
136
|
+
Parser parser = (Parser)((RubyClass)clazz).allocate();
|
137
|
+
|
138
|
+
parser.callInit(args, block);
|
139
|
+
|
140
|
+
return parser;
|
141
|
+
}
|
142
|
+
|
143
|
+
@JRubyMethod(required = 1, optional = 1, visibility = Visibility.PRIVATE)
|
144
|
+
public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
|
145
|
+
Ruby runtime = context.getRuntime();
|
146
|
+
RubyString source = convertEncoding(context, args[0].convertToString());
|
147
|
+
|
148
|
+
OptionsReader opts = new OptionsReader(context, args.length > 1 ? args[1] : null);
|
149
|
+
this.maxNesting = opts.getInt("max_nesting", DEFAULT_MAX_NESTING);
|
150
|
+
this.allowNaN = opts.getBool("allow_nan", false);
|
151
|
+
this.symbolizeNames = opts.getBool("symbolize_names", false);
|
152
|
+
this.createId = opts.getString("create_id", getCreateId(context));
|
153
|
+
this.createAdditions = opts.getBool("create_additions", true);
|
154
|
+
this.objectClass = opts.getClass("object_class", runtime.getHash());
|
155
|
+
this.arrayClass = opts.getClass("array_class", runtime.getArray());
|
156
|
+
this.match_string = opts.getHash("match_string");
|
157
|
+
|
158
|
+
this.vSource = source;
|
159
|
+
return this;
|
160
|
+
}
|
161
|
+
|
162
|
+
/**
|
163
|
+
* Checks the given string's encoding. If a non-UTF-8 encoding is detected,
|
164
|
+
* a converted copy is returned.
|
165
|
+
* Returns the source string if no conversion is needed.
|
166
|
+
*/
|
167
|
+
private RubyString convertEncoding(ThreadContext context, RubyString source) {
|
168
|
+
ByteList bl = source.getByteList();
|
169
|
+
int len = bl.length();
|
170
|
+
if (len < 2) {
|
171
|
+
throw Utils.newException(context, Utils.M_PARSER_ERROR,
|
172
|
+
"A JSON text must at least contain two octets!");
|
173
|
+
}
|
174
|
+
|
175
|
+
if (info.encodingsSupported()) {
|
176
|
+
RubyEncoding encoding = (RubyEncoding)source.encoding(context);
|
177
|
+
if (encoding != info.ascii8bit) {
|
178
|
+
return (RubyString)source.encode(context, info.utf8);
|
179
|
+
}
|
180
|
+
|
181
|
+
String sniffedEncoding = sniffByteList(bl);
|
182
|
+
if (sniffedEncoding == null) return source; // assume UTF-8
|
183
|
+
return reinterpretEncoding(context, source, sniffedEncoding);
|
184
|
+
}
|
185
|
+
|
186
|
+
String sniffedEncoding = sniffByteList(bl);
|
187
|
+
if (sniffedEncoding == null) return source; // assume UTF-8
|
188
|
+
Ruby runtime = context.getRuntime();
|
189
|
+
return (RubyString)info.jsonModule.
|
190
|
+
callMethod(context, "iconv",
|
191
|
+
new IRubyObject[] {
|
192
|
+
runtime.newString("utf-8"),
|
193
|
+
runtime.newString(sniffedEncoding),
|
194
|
+
source});
|
195
|
+
}
|
196
|
+
|
197
|
+
/**
|
198
|
+
* Checks the first four bytes of the given ByteList to infer its encoding,
|
199
|
+
* using the principle demonstrated on section 3 of RFC 4627 (JSON).
|
200
|
+
*/
|
201
|
+
private static String sniffByteList(ByteList bl) {
|
202
|
+
if (bl.length() < 4) return null;
|
203
|
+
if (bl.get(0) == 0 && bl.get(2) == 0) {
|
204
|
+
return bl.get(1) == 0 ? "utf-32be" : "utf-16be";
|
205
|
+
}
|
206
|
+
if (bl.get(1) == 0 && bl.get(3) == 0) {
|
207
|
+
return bl.get(2) == 0 ? "utf-32le" : "utf-16le";
|
208
|
+
}
|
209
|
+
return null;
|
210
|
+
}
|
211
|
+
|
212
|
+
/**
|
213
|
+
* Assumes the given (binary) RubyString to be in the given encoding, then
|
214
|
+
* converts it to UTF-8.
|
215
|
+
*/
|
216
|
+
private RubyString reinterpretEncoding(ThreadContext context,
|
217
|
+
RubyString str, String sniffedEncoding) {
|
218
|
+
RubyEncoding actualEncoding = info.getEncoding(context, sniffedEncoding);
|
219
|
+
RubyEncoding targetEncoding = info.utf8;
|
220
|
+
RubyString dup = (RubyString)str.dup();
|
221
|
+
dup.force_encoding(context, actualEncoding);
|
222
|
+
return (RubyString)dup.encode_bang(context, targetEncoding);
|
223
|
+
}
|
224
|
+
|
225
|
+
/**
|
226
|
+
* <code>Parser#parse()</code>
|
227
|
+
*
|
228
|
+
* <p>Parses the current JSON text <code>source</code> and returns the
|
229
|
+
* complete data structure as a result.
|
230
|
+
*/
|
231
|
+
@JRubyMethod
|
232
|
+
public IRubyObject parse(ThreadContext context) {
|
233
|
+
return new ParserSession(this, context).parse();
|
234
|
+
}
|
235
|
+
|
236
|
+
/**
|
237
|
+
* <code>Parser#source()</code>
|
238
|
+
*
|
239
|
+
* <p>Returns a copy of the current <code>source</code> string, that was
|
240
|
+
* used to construct this Parser.
|
241
|
+
*/
|
242
|
+
@JRubyMethod(name = "source")
|
243
|
+
public IRubyObject source_get() {
|
244
|
+
return vSource.dup();
|
245
|
+
}
|
246
|
+
|
247
|
+
/**
|
248
|
+
* Queries <code>JSON.create_id</code>. Returns <code>null</code> if it is
|
249
|
+
* set to <code>nil</code> or <code>false</code>, and a String if not.
|
250
|
+
*/
|
251
|
+
private RubyString getCreateId(ThreadContext context) {
|
252
|
+
IRubyObject v = info.jsonModule.callMethod(context, "create_id");
|
253
|
+
return v.isTrue() ? v.convertToString() : null;
|
254
|
+
}
|
255
|
+
|
256
|
+
/**
|
257
|
+
* A string parsing session.
|
258
|
+
*
|
259
|
+
* <p>Once a ParserSession is instantiated, the source string should not
|
260
|
+
* change until the parsing is complete. The ParserSession object assumes
|
261
|
+
* the source {@link RubyString} is still associated to its original
|
262
|
+
* {@link ByteList}, which in turn must still be bound to the same
|
263
|
+
* <code>byte[]</code> value (and on the same offset).
|
264
|
+
*/
|
265
|
+
// Ragel uses lots of fall-through
|
266
|
+
@SuppressWarnings("fallthrough")
|
267
|
+
private static class ParserSession {
|
268
|
+
private final Parser parser;
|
269
|
+
private final ThreadContext context;
|
270
|
+
private final ByteList byteList;
|
271
|
+
private final byte[] data;
|
272
|
+
private final StringDecoder decoder;
|
273
|
+
private int currentNesting = 0;
|
274
|
+
|
275
|
+
// initialization value for all state variables.
|
276
|
+
// no idea about the origins of this value, ask Flori ;)
|
277
|
+
private static final int EVIL = 0x666;
|
278
|
+
|
279
|
+
private ParserSession(Parser parser, ThreadContext context) {
|
280
|
+
this.parser = parser;
|
281
|
+
this.context = context;
|
282
|
+
this.byteList = parser.vSource.getByteList();
|
283
|
+
this.data = byteList.unsafeBytes();
|
284
|
+
this.decoder = new StringDecoder(context);
|
285
|
+
}
|
286
|
+
|
287
|
+
private RaiseException unexpectedToken(int absStart, int absEnd) {
|
288
|
+
RubyString msg = getRuntime().newString("unexpected token at '")
|
289
|
+
.cat(data, absStart, absEnd - absStart)
|
290
|
+
.cat((byte)'\'');
|
291
|
+
return newException(Utils.M_PARSER_ERROR, msg);
|
292
|
+
}
|
293
|
+
|
294
|
+
private Ruby getRuntime() {
|
295
|
+
return context.getRuntime();
|
296
|
+
}
|
297
|
+
|
298
|
+
%%{
|
299
|
+
machine JSON_common;
|
300
|
+
|
301
|
+
cr = '\n';
|
302
|
+
cr_neg = [^\n];
|
303
|
+
ws = [ \t\r\n];
|
304
|
+
c_comment = '/*' ( any* - (any* '*/' any* ) ) '*/';
|
305
|
+
cpp_comment = '//' cr_neg* cr;
|
306
|
+
comment = c_comment | cpp_comment;
|
307
|
+
ignore = ws | comment;
|
308
|
+
name_separator = ':';
|
309
|
+
value_separator = ',';
|
310
|
+
Vnull = 'null';
|
311
|
+
Vfalse = 'false';
|
312
|
+
Vtrue = 'true';
|
313
|
+
VNaN = 'NaN';
|
314
|
+
VInfinity = 'Infinity';
|
315
|
+
VMinusInfinity = '-Infinity';
|
316
|
+
begin_value = [nft"\-[{NI] | digit;
|
317
|
+
begin_object = '{';
|
318
|
+
end_object = '}';
|
319
|
+
begin_array = '[';
|
320
|
+
end_array = ']';
|
321
|
+
begin_string = '"';
|
322
|
+
begin_name = begin_string;
|
323
|
+
begin_number = digit | '-';
|
324
|
+
}%%
|
325
|
+
|
326
|
+
%%{
|
327
|
+
machine JSON_value;
|
328
|
+
include JSON_common;
|
329
|
+
|
330
|
+
write data;
|
331
|
+
|
332
|
+
action parse_null {
|
333
|
+
result = getRuntime().getNil();
|
334
|
+
}
|
335
|
+
action parse_false {
|
336
|
+
result = getRuntime().getFalse();
|
337
|
+
}
|
338
|
+
action parse_true {
|
339
|
+
result = getRuntime().getTrue();
|
340
|
+
}
|
341
|
+
action parse_nan {
|
342
|
+
if (parser.allowNaN) {
|
343
|
+
result = getConstant(CONST_NAN);
|
344
|
+
} else {
|
345
|
+
throw unexpectedToken(p - 2, pe);
|
346
|
+
}
|
347
|
+
}
|
348
|
+
action parse_infinity {
|
349
|
+
if (parser.allowNaN) {
|
350
|
+
result = getConstant(CONST_INFINITY);
|
351
|
+
} else {
|
352
|
+
throw unexpectedToken(p - 7, pe);
|
353
|
+
}
|
354
|
+
}
|
355
|
+
action parse_number {
|
356
|
+
if (pe > fpc + 9 &&
|
357
|
+
absSubSequence(fpc, fpc + 9).toString().equals(JSON_MINUS_INFINITY)) {
|
358
|
+
|
359
|
+
if (parser.allowNaN) {
|
360
|
+
result = getConstant(CONST_MINUS_INFINITY);
|
361
|
+
fexec p + 10;
|
362
|
+
fhold;
|
363
|
+
fbreak;
|
364
|
+
} else {
|
365
|
+
throw unexpectedToken(p, pe);
|
366
|
+
}
|
367
|
+
}
|
368
|
+
ParserResult res = parseFloat(fpc, pe);
|
369
|
+
if (res != null) {
|
370
|
+
result = res.result;
|
371
|
+
fexec res.p;
|
372
|
+
}
|
373
|
+
res = parseInteger(fpc, pe);
|
374
|
+
if (res != null) {
|
375
|
+
result = res.result;
|
376
|
+
fexec res.p;
|
377
|
+
}
|
378
|
+
fhold;
|
379
|
+
fbreak;
|
380
|
+
}
|
381
|
+
action parse_string {
|
382
|
+
ParserResult res = parseString(fpc, pe);
|
383
|
+
if (res == null) {
|
384
|
+
fhold;
|
385
|
+
fbreak;
|
386
|
+
} else {
|
387
|
+
result = res.result;
|
388
|
+
fexec res.p;
|
389
|
+
}
|
390
|
+
}
|
391
|
+
action parse_array {
|
392
|
+
currentNesting++;
|
393
|
+
ParserResult res = parseArray(fpc, pe);
|
394
|
+
currentNesting--;
|
395
|
+
if (res == null) {
|
396
|
+
fhold;
|
397
|
+
fbreak;
|
398
|
+
} else {
|
399
|
+
result = res.result;
|
400
|
+
fexec res.p;
|
401
|
+
}
|
402
|
+
}
|
403
|
+
action parse_object {
|
404
|
+
currentNesting++;
|
405
|
+
ParserResult res = parseObject(fpc, pe);
|
406
|
+
currentNesting--;
|
407
|
+
if (res == null) {
|
408
|
+
fhold;
|
409
|
+
fbreak;
|
410
|
+
} else {
|
411
|
+
result = res.result;
|
412
|
+
fexec res.p;
|
413
|
+
}
|
414
|
+
}
|
415
|
+
action exit {
|
416
|
+
fhold;
|
417
|
+
fbreak;
|
418
|
+
}
|
419
|
+
|
420
|
+
main := ( Vnull @parse_null |
|
421
|
+
Vfalse @parse_false |
|
422
|
+
Vtrue @parse_true |
|
423
|
+
VNaN @parse_nan |
|
424
|
+
VInfinity @parse_infinity |
|
425
|
+
begin_number >parse_number |
|
426
|
+
begin_string >parse_string |
|
427
|
+
begin_array >parse_array |
|
428
|
+
begin_object >parse_object
|
429
|
+
) %*exit;
|
430
|
+
}%%
|
431
|
+
|
432
|
+
ParserResult parseValue(int p, int pe) {
|
433
|
+
int cs = EVIL;
|
434
|
+
IRubyObject result = null;
|
435
|
+
|
436
|
+
%% write init;
|
437
|
+
%% write exec;
|
438
|
+
|
439
|
+
if (cs >= JSON_value_first_final && result != null) {
|
440
|
+
return new ParserResult(result, p);
|
441
|
+
} else {
|
442
|
+
return null;
|
443
|
+
}
|
444
|
+
}
|
445
|
+
|
446
|
+
%%{
|
447
|
+
machine JSON_integer;
|
448
|
+
|
449
|
+
write data;
|
450
|
+
|
451
|
+
action exit {
|
452
|
+
fhold;
|
453
|
+
fbreak;
|
454
|
+
}
|
455
|
+
|
456
|
+
main := '-'? ( '0' | [1-9][0-9]* ) ( ^[0-9] @exit );
|
457
|
+
}%%
|
458
|
+
|
459
|
+
ParserResult parseInteger(int p, int pe) {
|
460
|
+
int cs = EVIL;
|
461
|
+
|
462
|
+
%% write init;
|
463
|
+
int memo = p;
|
464
|
+
%% write exec;
|
465
|
+
|
466
|
+
if (cs < JSON_integer_first_final) {
|
467
|
+
return null;
|
468
|
+
}
|
469
|
+
|
470
|
+
ByteList num = absSubSequence(memo, p);
|
471
|
+
// note: this is actually a shared string, but since it is temporary and
|
472
|
+
// read-only, it doesn't really matter
|
473
|
+
RubyString expr = RubyString.newStringLight(getRuntime(), num);
|
474
|
+
RubyInteger number = RubyNumeric.str2inum(getRuntime(), expr, 10, true);
|
475
|
+
return new ParserResult(number, p + 1);
|
476
|
+
}
|
477
|
+
|
478
|
+
%%{
|
479
|
+
machine JSON_float;
|
480
|
+
include JSON_common;
|
481
|
+
|
482
|
+
write data;
|
483
|
+
|
484
|
+
action exit {
|
485
|
+
fhold;
|
486
|
+
fbreak;
|
487
|
+
}
|
488
|
+
|
489
|
+
main := '-'?
|
490
|
+
( ( ( '0' | [1-9][0-9]* ) '.' [0-9]+ ( [Ee] [+\-]?[0-9]+ )? )
|
491
|
+
| ( ( '0' | [1-9][0-9]* ) ( [Ee] [+\-]? [0-9]+ ) ) )
|
492
|
+
( ^[0-9Ee.\-] @exit );
|
493
|
+
}%%
|
494
|
+
|
495
|
+
ParserResult parseFloat(int p, int pe) {
|
496
|
+
int cs = EVIL;
|
497
|
+
|
498
|
+
%% write init;
|
499
|
+
int memo = p;
|
500
|
+
%% write exec;
|
501
|
+
|
502
|
+
if (cs < JSON_float_first_final) {
|
503
|
+
return null;
|
504
|
+
}
|
505
|
+
|
506
|
+
ByteList num = absSubSequence(memo, p);
|
507
|
+
// note: this is actually a shared string, but since it is temporary and
|
508
|
+
// read-only, it doesn't really matter
|
509
|
+
RubyString expr = RubyString.newStringLight(getRuntime(), num);
|
510
|
+
RubyFloat number = RubyNumeric.str2fnum(getRuntime(), expr, true);
|
511
|
+
return new ParserResult(number, p + 1);
|
512
|
+
}
|
513
|
+
|
514
|
+
%%{
|
515
|
+
machine JSON_string;
|
516
|
+
include JSON_common;
|
517
|
+
|
518
|
+
write data;
|
519
|
+
|
520
|
+
action parse_string {
|
521
|
+
int offset = byteList.begin();
|
522
|
+
ByteList decoded = decoder.decode(byteList, memo + 1 - offset,
|
523
|
+
p - offset);
|
524
|
+
result = getRuntime().newString(decoded);
|
525
|
+
if (result == null) {
|
526
|
+
fhold;
|
527
|
+
fbreak;
|
528
|
+
} else {
|
529
|
+
fexec p + 1;
|
530
|
+
}
|
531
|
+
}
|
532
|
+
|
533
|
+
action exit {
|
534
|
+
fhold;
|
535
|
+
fbreak;
|
536
|
+
}
|
537
|
+
|
538
|
+
main := '"'
|
539
|
+
( ( ^(["\\]|0..0x1f)
|
540
|
+
| '\\'["\\/bfnrt]
|
541
|
+
| '\\u'[0-9a-fA-F]{4}
|
542
|
+
| '\\'^(["\\/bfnrtu]|0..0x1f)
|
543
|
+
)* %parse_string
|
544
|
+
) '"' @exit;
|
545
|
+
}%%
|
546
|
+
|
547
|
+
ParserResult parseString(int p, int pe) {
|
548
|
+
int cs = EVIL;
|
549
|
+
IRubyObject result = null;
|
550
|
+
|
551
|
+
%% write init;
|
552
|
+
int memo = p;
|
553
|
+
%% write exec;
|
554
|
+
|
555
|
+
if (parser.createAdditions) {
|
556
|
+
RubyHash match_string = parser.match_string;
|
557
|
+
if (match_string != null) {
|
558
|
+
final IRubyObject[] memoArray = { result, null };
|
559
|
+
try {
|
560
|
+
match_string.visitAll(new RubyHash.Visitor() {
|
561
|
+
@Override
|
562
|
+
public void visit(IRubyObject pattern, IRubyObject klass) {
|
563
|
+
if (pattern.callMethod(context, "===", memoArray[0]).isTrue()) {
|
564
|
+
memoArray[1] = klass;
|
565
|
+
throw JumpException.SPECIAL_JUMP;
|
566
|
+
}
|
567
|
+
}
|
568
|
+
});
|
569
|
+
} catch (JumpException e) { }
|
570
|
+
if (memoArray[1] != null) {
|
571
|
+
RubyClass klass = (RubyClass) memoArray[1];
|
572
|
+
if (klass.respondsTo("json_creatable?") &&
|
573
|
+
klass.callMethod(context, "json_creatable?").isTrue()) {
|
574
|
+
result = klass.callMethod(context, "json_create", result);
|
575
|
+
}
|
576
|
+
}
|
577
|
+
}
|
578
|
+
}
|
579
|
+
|
580
|
+
if (cs >= JSON_string_first_final && result != null) {
|
581
|
+
return new ParserResult(result, p + 1);
|
582
|
+
} else {
|
583
|
+
return null;
|
584
|
+
}
|
585
|
+
}
|
586
|
+
|
587
|
+
%%{
|
588
|
+
machine JSON_array;
|
589
|
+
include JSON_common;
|
590
|
+
|
591
|
+
write data;
|
592
|
+
|
593
|
+
action parse_value {
|
594
|
+
ParserResult res = parseValue(fpc, pe);
|
595
|
+
if (res == null) {
|
596
|
+
fhold;
|
597
|
+
fbreak;
|
598
|
+
} else {
|
599
|
+
result.append(res.result);
|
600
|
+
fexec res.p;
|
601
|
+
}
|
602
|
+
}
|
603
|
+
|
604
|
+
action exit {
|
605
|
+
fhold;
|
606
|
+
fbreak;
|
607
|
+
}
|
608
|
+
|
609
|
+
next_element = value_separator ignore* begin_value >parse_value;
|
610
|
+
|
611
|
+
main := begin_array
|
612
|
+
ignore*
|
613
|
+
( ( begin_value >parse_value
|
614
|
+
ignore* )
|
615
|
+
( ignore*
|
616
|
+
next_element
|
617
|
+
ignore* )* )?
|
618
|
+
ignore*
|
619
|
+
end_array @exit;
|
620
|
+
}%%
|
621
|
+
|
622
|
+
ParserResult parseArray(int p, int pe) {
|
623
|
+
int cs = EVIL;
|
624
|
+
|
625
|
+
if (parser.maxNesting > 0 && currentNesting > parser.maxNesting) {
|
626
|
+
throw newException(Utils.M_NESTING_ERROR,
|
627
|
+
"nesting of " + currentNesting + " is too deep");
|
628
|
+
}
|
629
|
+
|
630
|
+
// this is guaranteed to be a RubyArray due to the earlier
|
631
|
+
// allocator test at OptionsReader#getClass
|
632
|
+
RubyArray result =
|
633
|
+
(RubyArray)parser.arrayClass.newInstance(context,
|
634
|
+
IRubyObject.NULL_ARRAY, Block.NULL_BLOCK);
|
635
|
+
|
636
|
+
%% write init;
|
637
|
+
%% write exec;
|
638
|
+
|
639
|
+
if (cs >= JSON_array_first_final) {
|
640
|
+
return new ParserResult(result, p + 1);
|
641
|
+
} else {
|
642
|
+
throw unexpectedToken(p, pe);
|
643
|
+
}
|
644
|
+
}
|
645
|
+
|
646
|
+
%%{
|
647
|
+
machine JSON_object;
|
648
|
+
include JSON_common;
|
649
|
+
|
650
|
+
write data;
|
651
|
+
|
652
|
+
action parse_value {
|
653
|
+
ParserResult res = parseValue(fpc, pe);
|
654
|
+
if (res == null) {
|
655
|
+
fhold;
|
656
|
+
fbreak;
|
657
|
+
} else {
|
658
|
+
result.op_aset(context, lastName, res.result);
|
659
|
+
fexec res.p;
|
660
|
+
}
|
661
|
+
}
|
662
|
+
|
663
|
+
action parse_name {
|
664
|
+
ParserResult res = parseString(fpc, pe);
|
665
|
+
if (res == null) {
|
666
|
+
fhold;
|
667
|
+
fbreak;
|
668
|
+
} else {
|
669
|
+
RubyString name = (RubyString)res.result;
|
670
|
+
if (parser.symbolizeNames) {
|
671
|
+
lastName = context.getRuntime().is1_9()
|
672
|
+
? name.intern19()
|
673
|
+
: name.intern();
|
674
|
+
} else {
|
675
|
+
lastName = name;
|
676
|
+
}
|
677
|
+
fexec res.p;
|
678
|
+
}
|
679
|
+
}
|
680
|
+
|
681
|
+
action exit {
|
682
|
+
fhold;
|
683
|
+
fbreak;
|
684
|
+
}
|
685
|
+
|
686
|
+
a_pair = ignore*
|
687
|
+
begin_name >parse_name
|
688
|
+
ignore* name_separator ignore*
|
689
|
+
begin_value >parse_value;
|
690
|
+
|
691
|
+
main := begin_object
|
692
|
+
(a_pair (ignore* value_separator a_pair)*)?
|
693
|
+
ignore* end_object @exit;
|
694
|
+
}%%
|
695
|
+
|
696
|
+
ParserResult parseObject(int p, int pe) {
|
697
|
+
int cs = EVIL;
|
698
|
+
IRubyObject lastName = null;
|
699
|
+
|
700
|
+
if (parser.maxNesting > 0 && currentNesting > parser.maxNesting) {
|
701
|
+
throw newException(Utils.M_NESTING_ERROR,
|
702
|
+
"nesting of " + currentNesting + " is too deep");
|
703
|
+
}
|
704
|
+
|
705
|
+
// this is guaranteed to be a RubyHash due to the earlier
|
706
|
+
// allocator test at OptionsReader#getClass
|
707
|
+
RubyHash result =
|
708
|
+
(RubyHash)parser.objectClass.newInstance(context,
|
709
|
+
IRubyObject.NULL_ARRAY, Block.NULL_BLOCK);
|
710
|
+
|
711
|
+
%% write init;
|
712
|
+
%% write exec;
|
713
|
+
|
714
|
+
if (cs < JSON_object_first_final) {
|
715
|
+
return null;
|
716
|
+
}
|
717
|
+
|
718
|
+
IRubyObject returnedResult = result;
|
719
|
+
|
720
|
+
// attempt to de-serialize object
|
721
|
+
if (parser.createAdditions) {
|
722
|
+
IRubyObject vKlassName = result.op_aref(context, parser.createId);
|
723
|
+
if (!vKlassName.isNil()) {
|
724
|
+
// might throw ArgumentError, we let it propagate
|
725
|
+
IRubyObject klass = parser.info.jsonModule.
|
726
|
+
callMethod(context, "deep_const_get", vKlassName);
|
727
|
+
if (klass.respondsTo("json_creatable?") &&
|
728
|
+
klass.callMethod(context, "json_creatable?").isTrue()) {
|
729
|
+
|
730
|
+
returnedResult = klass.callMethod(context, "json_create", result);
|
731
|
+
}
|
732
|
+
}
|
733
|
+
}
|
734
|
+
return new ParserResult(returnedResult, p + 1);
|
735
|
+
}
|
736
|
+
|
737
|
+
%%{
|
738
|
+
machine JSON;
|
739
|
+
include JSON_common;
|
740
|
+
|
741
|
+
write data;
|
742
|
+
|
743
|
+
action parse_object {
|
744
|
+
currentNesting = 1;
|
745
|
+
ParserResult res = parseObject(fpc, pe);
|
746
|
+
if (res == null) {
|
747
|
+
fhold;
|
748
|
+
fbreak;
|
749
|
+
} else {
|
750
|
+
result = res.result;
|
751
|
+
fexec res.p;
|
752
|
+
}
|
753
|
+
}
|
754
|
+
|
755
|
+
action parse_array {
|
756
|
+
currentNesting = 1;
|
757
|
+
ParserResult res = parseArray(fpc, pe);
|
758
|
+
if (res == null) {
|
759
|
+
fhold;
|
760
|
+
fbreak;
|
761
|
+
} else {
|
762
|
+
result = res.result;
|
763
|
+
fexec res.p;
|
764
|
+
}
|
765
|
+
}
|
766
|
+
|
767
|
+
main := ignore*
|
768
|
+
( begin_object >parse_object
|
769
|
+
| begin_array >parse_array )
|
770
|
+
ignore*;
|
771
|
+
}%%
|
772
|
+
|
773
|
+
public IRubyObject parse() {
|
774
|
+
int cs = EVIL;
|
775
|
+
int p, pe;
|
776
|
+
IRubyObject result = null;
|
777
|
+
|
778
|
+
%% write init;
|
779
|
+
p = byteList.begin();
|
780
|
+
pe = p + byteList.length();
|
781
|
+
%% write exec;
|
782
|
+
|
783
|
+
if (cs >= JSON_first_final && p == pe) {
|
784
|
+
return result;
|
785
|
+
} else {
|
786
|
+
throw unexpectedToken(p, pe);
|
787
|
+
}
|
788
|
+
}
|
789
|
+
|
790
|
+
/**
|
791
|
+
* Returns a subsequence of the source ByteList, based on source
|
792
|
+
* array byte offsets (i.e., the ByteList's own begin offset is not
|
793
|
+
* automatically added).
|
794
|
+
* @param start
|
795
|
+
* @param end
|
796
|
+
*/
|
797
|
+
private ByteList absSubSequence(int absStart, int absEnd) {
|
798
|
+
int offset = byteList.begin();
|
799
|
+
return (ByteList)byteList.subSequence(absStart - offset,
|
800
|
+
absEnd - offset);
|
801
|
+
}
|
802
|
+
|
803
|
+
/**
|
804
|
+
* Retrieves a constant directly descended from the <code>JSON</code> module.
|
805
|
+
* @param name The constant name
|
806
|
+
*/
|
807
|
+
private IRubyObject getConstant(String name) {
|
808
|
+
return parser.info.jsonModule.getConstant(name);
|
809
|
+
}
|
810
|
+
|
811
|
+
private RaiseException newException(String className, String message) {
|
812
|
+
return Utils.newException(context, className, message);
|
813
|
+
}
|
814
|
+
|
815
|
+
private RaiseException newException(String className, RubyString message) {
|
816
|
+
return Utils.newException(context, className, message);
|
817
|
+
}
|
818
|
+
|
819
|
+
private RaiseException newException(String className,
|
820
|
+
String messageBegin, ByteList messageEnd) {
|
821
|
+
return newException(className,
|
822
|
+
getRuntime().newString(messageBegin).cat(messageEnd));
|
823
|
+
}
|
824
|
+
}
|
825
|
+
}
|