json_pure 1.4.6 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }