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.

Files changed (48) hide show
  1. data/CHANGES +6 -0
  2. data/COPYING-json-jruby +57 -0
  3. data/README-json-jruby.markdown +33 -0
  4. data/Rakefile +224 -119
  5. data/VERSION +1 -1
  6. data/benchmarks/generator2_benchmark.rb +1 -1
  7. data/benchmarks/generator_benchmark.rb +1 -1
  8. data/ext/json/ext/generator/generator.c +20 -20
  9. data/ext/json/ext/generator/generator.h +7 -7
  10. data/ext/json/ext/parser/extconf.rb +1 -0
  11. data/ext/json/ext/parser/parser.c +122 -88
  12. data/ext/json/ext/parser/parser.h +7 -0
  13. data/ext/json/ext/parser/parser.rl +54 -20
  14. data/java/lib/bytelist-1.0.6.jar +0 -0
  15. data/java/lib/jcodings.jar +0 -0
  16. data/java/src/json/ext/ByteListTranscoder.java +167 -0
  17. data/java/src/json/ext/Generator.java +441 -0
  18. data/java/src/json/ext/GeneratorMethods.java +231 -0
  19. data/java/src/json/ext/GeneratorService.java +42 -0
  20. data/java/src/json/ext/GeneratorState.java +473 -0
  21. data/java/src/json/ext/OptionsReader.java +119 -0
  22. data/java/src/json/ext/Parser.java +2295 -0
  23. data/java/src/json/ext/Parser.rl +825 -0
  24. data/java/src/json/ext/ParserService.java +34 -0
  25. data/java/src/json/ext/RuntimeInfo.java +119 -0
  26. data/java/src/json/ext/StringDecoder.java +166 -0
  27. data/java/src/json/ext/StringEncoder.java +106 -0
  28. data/java/src/json/ext/Utils.java +89 -0
  29. data/json-java.gemspec +20 -0
  30. data/lib/json/add/core.rb +1 -2
  31. data/lib/json/add/rails.rb +4 -54
  32. data/lib/json/common.rb +36 -8
  33. data/lib/json/editor.rb +1 -3
  34. data/lib/json/ext.rb +2 -2
  35. data/lib/json/pure.rb +2 -64
  36. data/lib/json/pure/generator.rb +10 -8
  37. data/lib/json/pure/parser.rb +23 -12
  38. data/lib/json/version.rb +1 -1
  39. data/tests/setup_variant.rb +11 -0
  40. data/tests/test_json.rb +1 -5
  41. data/tests/test_json_addition.rb +14 -9
  42. data/tests/test_json_encoding.rb +9 -12
  43. data/tests/test_json_fixtures.rb +9 -8
  44. data/tests/test_json_generate.rb +3 -5
  45. data/tests/test_json_string_matching.rb +40 -0
  46. data/tests/test_json_unicode.rb +1 -5
  47. metadata +51 -13
  48. 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
+ }