json 1.0.3-mswin32

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 (84) hide show
  1. data/CHANGES +32 -0
  2. data/GPL +340 -0
  3. data/README +77 -0
  4. data/Rakefile +304 -0
  5. data/TODO +1 -0
  6. data/VERSION +1 -0
  7. data/benchmarks/benchmark.txt +133 -0
  8. data/benchmarks/benchmark_generator.rb +44 -0
  9. data/benchmarks/benchmark_parser.rb +22 -0
  10. data/benchmarks/benchmark_rails.rb +26 -0
  11. data/bin/edit_json.rb +11 -0
  12. data/bin/prettify_json.rb +75 -0
  13. data/data/example.json +1 -0
  14. data/data/index.html +37 -0
  15. data/data/prototype.js +2515 -0
  16. data/ext/json/ext/generator.so +0 -0
  17. data/ext/json/ext/generator/extconf.rb +9 -0
  18. data/ext/json/ext/generator/generator.c +729 -0
  19. data/ext/json/ext/generator/unicode.c +184 -0
  20. data/ext/json/ext/generator/unicode.h +40 -0
  21. data/ext/json/ext/parser.so +0 -0
  22. data/ext/json/ext/parser/extconf.rb +9 -0
  23. data/ext/json/ext/parser/parser.c +1554 -0
  24. data/ext/json/ext/parser/parser.rl +515 -0
  25. data/ext/json/ext/parser/unicode.c +156 -0
  26. data/ext/json/ext/parser/unicode.h +44 -0
  27. data/install.rb +26 -0
  28. data/lib/json.rb +205 -0
  29. data/lib/json/Array.xpm +21 -0
  30. data/lib/json/FalseClass.xpm +21 -0
  31. data/lib/json/Hash.xpm +21 -0
  32. data/lib/json/Key.xpm +73 -0
  33. data/lib/json/NilClass.xpm +21 -0
  34. data/lib/json/Numeric.xpm +28 -0
  35. data/lib/json/String.xpm +96 -0
  36. data/lib/json/TrueClass.xpm +21 -0
  37. data/lib/json/common.rb +184 -0
  38. data/lib/json/editor.rb +1207 -0
  39. data/lib/json/ext.rb +13 -0
  40. data/lib/json/json.xpm +1499 -0
  41. data/lib/json/pure.rb +75 -0
  42. data/lib/json/pure/generator.rb +321 -0
  43. data/lib/json/pure/parser.rb +214 -0
  44. data/lib/json/version.rb +8 -0
  45. data/tests/fixtures/fail1.json +1 -0
  46. data/tests/fixtures/fail10.json +1 -0
  47. data/tests/fixtures/fail11.json +1 -0
  48. data/tests/fixtures/fail12.json +1 -0
  49. data/tests/fixtures/fail13.json +1 -0
  50. data/tests/fixtures/fail14.json +1 -0
  51. data/tests/fixtures/fail15.json +1 -0
  52. data/tests/fixtures/fail16.json +1 -0
  53. data/tests/fixtures/fail17.json +1 -0
  54. data/tests/fixtures/fail19.json +1 -0
  55. data/tests/fixtures/fail2.json +1 -0
  56. data/tests/fixtures/fail20.json +1 -0
  57. data/tests/fixtures/fail21.json +1 -0
  58. data/tests/fixtures/fail22.json +1 -0
  59. data/tests/fixtures/fail23.json +1 -0
  60. data/tests/fixtures/fail24.json +1 -0
  61. data/tests/fixtures/fail25.json +1 -0
  62. data/tests/fixtures/fail26.json +1 -0
  63. data/tests/fixtures/fail27.json +2 -0
  64. data/tests/fixtures/fail28.json +2 -0
  65. data/tests/fixtures/fail3.json +1 -0
  66. data/tests/fixtures/fail4.json +1 -0
  67. data/tests/fixtures/fail5.json +1 -0
  68. data/tests/fixtures/fail6.json +1 -0
  69. data/tests/fixtures/fail7.json +1 -0
  70. data/tests/fixtures/fail8.json +1 -0
  71. data/tests/fixtures/fail9.json +1 -0
  72. data/tests/fixtures/pass1.json +56 -0
  73. data/tests/fixtures/pass18.json +1 -0
  74. data/tests/fixtures/pass2.json +1 -0
  75. data/tests/fixtures/pass3.json +6 -0
  76. data/tests/runner.rb +24 -0
  77. data/tests/test_json.rb +236 -0
  78. data/tests/test_json_addition.rb +94 -0
  79. data/tests/test_json_fixtures.rb +30 -0
  80. data/tests/test_json_generate.rb +81 -0
  81. data/tests/test_json_unicode.rb +58 -0
  82. data/tools/fuzz.rb +131 -0
  83. data/tools/server.rb +62 -0
  84. metadata +149 -0
@@ -0,0 +1,75 @@
1
+ require 'json/common'
2
+ require 'json/pure/parser'
3
+ require 'json/pure/generator'
4
+
5
+ module JSON
6
+ begin
7
+ require 'iconv'
8
+ # An iconv instance to convert from UTF8 to UTF16 Big Endian.
9
+ UTF16toUTF8 = Iconv.new('utf-8', 'utf-16be') # :nodoc:
10
+ # An iconv instance to convert from UTF16 Big Endian to UTF8.
11
+ UTF8toUTF16 = Iconv.new('utf-16be', 'utf-8') # :nodoc:
12
+ UTF8toUTF16.iconv('no bom')
13
+ rescue Errno::EINVAL, Iconv::InvalidEncoding
14
+ # Iconv doesn't support big endian utf-16. Let's try to hack this manually
15
+ # into the converters.
16
+ begin
17
+ old_verbose, $VERBSOSE = $VERBOSE, nil
18
+ # An iconv instance to convert from UTF8 to UTF16 Big Endian.
19
+ UTF16toUTF8 = Iconv.new('utf-8', 'utf-16') # :nodoc:
20
+ # An iconv instance to convert from UTF16 Big Endian to UTF8.
21
+ UTF8toUTF16 = Iconv.new('utf-16', 'utf-8') # :nodoc:
22
+ UTF8toUTF16.iconv('no bom')
23
+ if UTF8toUTF16.iconv("\xe2\x82\xac") == "\xac\x20"
24
+ swapper = Class.new do
25
+ def initialize(iconv) # :nodoc:
26
+ @iconv = iconv
27
+ end
28
+
29
+ def iconv(string) # :nodoc:
30
+ result = @iconv.iconv(string)
31
+ JSON.swap!(result)
32
+ end
33
+ end
34
+ UTF8toUTF16 = swapper.new(UTF8toUTF16) # :nodoc:
35
+ end
36
+ if UTF16toUTF8.iconv("\xac\x20") == "\xe2\x82\xac"
37
+ swapper = Class.new do
38
+ def initialize(iconv) # :nodoc:
39
+ @iconv = iconv
40
+ end
41
+
42
+ def iconv(string) # :nodoc:
43
+ string = JSON.swap!(string.dup)
44
+ @iconv.iconv(string)
45
+ end
46
+ end
47
+ UTF16toUTF8 = swapper.new(UTF16toUTF8) # :nodoc:
48
+ end
49
+ rescue Errno::EINVAL, Iconv::InvalidEncoding
50
+ raise MissingUnicodeSupport, "iconv doesn't seem to support UTF-8/UTF-16 conversions"
51
+ ensure
52
+ $VERBOSE = old_verbose
53
+ end
54
+ rescue LoadError
55
+ raise MissingUnicodeSupport,
56
+ "iconv couldn't be loaded, which is required for UTF-8/UTF-16 conversions"
57
+ end
58
+
59
+ # Swap consecutive bytes of _string_ in place.
60
+ def self.swap!(string) # :nodoc:
61
+ 0.upto(string.size / 2) do |i|
62
+ break unless string[2 * i + 1]
63
+ string[2 * i], string[2 * i + 1] = string[2 * i + 1], string[2 * i]
64
+ end
65
+ string
66
+ end
67
+
68
+ # This module holds all the modules/classes that implement JSON's
69
+ # functionality in pure ruby.
70
+ module Pure
71
+ $DEBUG and warn "Using pure library for JSON."
72
+ JSON.parser = Parser
73
+ JSON.generator = Generator
74
+ end
75
+ end
@@ -0,0 +1,321 @@
1
+ module JSON
2
+ MAP = {
3
+ "\x0" => '\u0000',
4
+ "\x1" => '\u0001',
5
+ "\x2" => '\u0002',
6
+ "\x3" => '\u0003',
7
+ "\x4" => '\u0004',
8
+ "\x5" => '\u0005',
9
+ "\x6" => '\u0006',
10
+ "\x7" => '\u0007',
11
+ "\b" => '\b',
12
+ "\t" => '\t',
13
+ "\n" => '\n',
14
+ "\xb" => '\u000b',
15
+ "\f" => '\f',
16
+ "\r" => '\r',
17
+ "\xe" => '\u000e',
18
+ "\xf" => '\u000f',
19
+ "\x10" => '\u0010',
20
+ "\x11" => '\u0011',
21
+ "\x12" => '\u0012',
22
+ "\x13" => '\u0013',
23
+ "\x14" => '\u0014',
24
+ "\x15" => '\u0015',
25
+ "\x16" => '\u0016',
26
+ "\x17" => '\u0017',
27
+ "\x18" => '\u0018',
28
+ "\x19" => '\u0019',
29
+ "\x1a" => '\u001a',
30
+ "\x1b" => '\u001b',
31
+ "\x1c" => '\u001c',
32
+ "\x1d" => '\u001d',
33
+ "\x1e" => '\u001e',
34
+ "\x1f" => '\u001f',
35
+ '"' => '\"',
36
+ '\\' => '\\\\',
37
+ '/' => '\/',
38
+ } # :nodoc:
39
+
40
+ # Convert a UTF8 encoded Ruby string _string_ to a JSON string, encoded with
41
+ # UTF16 big endian characters as \u????, and return it.
42
+ def utf8_to_json(string) # :nodoc:
43
+ string = string.gsub(/["\\\/\x0-\x1f]/) { |c| MAP[c] }
44
+ string.gsub!(/(
45
+ (?:
46
+ [\xc2-\xdf][\x80-\xbf] |
47
+ [\xe0-\xef][\x80-\xbf]{2} |
48
+ [\xf0-\xf4][\x80-\xbf]{3}
49
+ )+ |
50
+ [\x80-\xc1\xf5-\xff] # invalid
51
+ )/nx) { |c|
52
+ c.size == 1 and raise GeneratorError, "invalid utf8 byte: '#{c}'"
53
+ s = JSON::UTF8toUTF16.iconv(c).unpack('H*')[0]
54
+ s.gsub!(/.{4}/n, '\\\\u\&')
55
+ }
56
+ string
57
+ rescue Iconv::Failure => e
58
+ raise GeneratorError, "Caught #{e.class}: #{e}"
59
+ end
60
+ module_function :utf8_to_json
61
+
62
+ module Pure
63
+ module Generator
64
+ # This class is used to create State instances, that are use to hold data
65
+ # while generating a JSON text from a a Ruby data structure.
66
+ class State
67
+ # Creates a State object from _opts_, which ought to be Hash to create
68
+ # a new State instance configured by _opts_, something else to create
69
+ # an unconfigured instance. If _opts_ is a State object, it is just
70
+ # returned.
71
+ def self.from_state(opts)
72
+ case opts
73
+ when self
74
+ opts
75
+ when Hash
76
+ new(opts)
77
+ else
78
+ new
79
+ end
80
+ end
81
+
82
+ # Instantiates a new State object, configured by _opts_.
83
+ #
84
+ # _opts_ can have the following keys:
85
+ #
86
+ # * *indent*: a string used to indent levels (default: ''),
87
+ # * *space*: a string that is put after, a : or , delimiter (default: ''),
88
+ # * *space_before*: a string that is put before a : pair delimiter (default: ''),
89
+ # * *object_nl*: a string that is put at the end of a JSON object (default: ''),
90
+ # * *array_nl*: a string that is put at the end of a JSON array (default: ''),
91
+ # * *check_circular*: true if checking for circular data structures
92
+ # should be done, false (the default) otherwise.
93
+ def initialize(opts = {})
94
+ @indent = opts[:indent] || ''
95
+ @space = opts[:space] || ''
96
+ @space_before = opts[:space_before] || ''
97
+ @object_nl = opts[:object_nl] || ''
98
+ @array_nl = opts[:array_nl] || ''
99
+ @check_circular = !!(opts[:check_circular] || false)
100
+ @seen = {}
101
+ end
102
+
103
+ # This string is used to indent levels in the JSON text.
104
+ attr_accessor :indent
105
+
106
+ # This string is used to insert a space between the tokens in a JSON
107
+ # string.
108
+ attr_accessor :space
109
+
110
+ # This string is used to insert a space before the ':' in JSON objects.
111
+ attr_accessor :space_before
112
+
113
+ # This string is put at the end of a line that holds a JSON object (or
114
+ # Hash).
115
+ attr_accessor :object_nl
116
+
117
+ # This string is put at the end of a line that holds a JSON array.
118
+ attr_accessor :array_nl
119
+
120
+ # Returns true, if circular data structures should be checked,
121
+ # otherwise returns false.
122
+ def check_circular?
123
+ @check_circular
124
+ end
125
+
126
+ # Returns _true_, if _object_ was already seen during this generating
127
+ # run.
128
+ def seen?(object)
129
+ @seen.key?(object.__id__)
130
+ end
131
+
132
+ # Remember _object_, to find out if it was already encountered (if a
133
+ # cyclic data structure is if a cyclic data structure is rendered).
134
+ def remember(object)
135
+ @seen[object.__id__] = true
136
+ end
137
+
138
+ # Forget _object_ for this generating run.
139
+ def forget(object)
140
+ @seen.delete object.__id__
141
+ end
142
+ end
143
+
144
+ module GeneratorMethods
145
+ module Object
146
+ # Converts this object to a string (calling #to_s), converts
147
+ # it to a JSON string, and returns the result. This is a fallback, if no
148
+ # special method #to_json was defined for some object.
149
+ def to_json(*) to_s.to_json end
150
+ end
151
+
152
+ module Hash
153
+ # Returns a JSON string containing a JSON object, that is unparsed from
154
+ # this Hash instance.
155
+ # _state_ is a JSON::State object, that can also be used to configure the
156
+ # produced JSON string output further.
157
+ # _depth_ is used to find out nesting depth, to indent accordingly.
158
+ def to_json(state = nil, depth = 0, *)
159
+ if state
160
+ state = JSON.state.from_state(state)
161
+ json_check_circular(state) { json_transform(state, depth) }
162
+ else
163
+ json_transform(state, depth)
164
+ end
165
+ end
166
+
167
+ private
168
+
169
+ def json_check_circular(state)
170
+ if state
171
+ state.seen?(self) and raise JSON::CircularDatastructure,
172
+ "circular data structures not supported!"
173
+ state.remember self
174
+ end
175
+ yield
176
+ ensure
177
+ state and state.forget self
178
+ end
179
+
180
+ def json_shift(state, depth)
181
+ state and not state.object_nl.empty? or return ''
182
+ state.indent * depth
183
+ end
184
+
185
+ def json_transform(state, depth)
186
+ delim = ','
187
+ delim << state.object_nl if state
188
+ result = '{'
189
+ result << state.object_nl if state
190
+ result << map { |key,value|
191
+ s = json_shift(state, depth + 1)
192
+ s << key.to_s.to_json(state, depth + 1)
193
+ s << state.space_before if state
194
+ s << ':'
195
+ s << state.space if state
196
+ s << value.to_json(state, depth + 1)
197
+ }.join(delim)
198
+ result << state.object_nl if state
199
+ result << json_shift(state, depth)
200
+ result << '}'
201
+ result
202
+ end
203
+ end
204
+
205
+ module Array
206
+ # Returns a JSON string containing a JSON array, that is unparsed from
207
+ # this Array instance.
208
+ # _state_ is a JSON::State object, that can also be used to configure the
209
+ # produced JSON string output further.
210
+ # _depth_ is used to find out nesting depth, to indent accordingly.
211
+ def to_json(state = nil, depth = 0, *)
212
+ if state
213
+ state = JSON.state.from_state(state)
214
+ json_check_circular(state) { json_transform(state, depth) }
215
+ else
216
+ json_transform(state, depth)
217
+ end
218
+ end
219
+
220
+ private
221
+
222
+ def json_check_circular(state)
223
+ if state
224
+ state.seen?(self) and raise JSON::CircularDatastructure,
225
+ "circular data structures not supported!"
226
+ state.remember self
227
+ end
228
+ yield
229
+ ensure
230
+ state and state.forget self
231
+ end
232
+
233
+ def json_shift(state, depth)
234
+ state and not state.array_nl.empty? or return ''
235
+ state.indent * depth
236
+ end
237
+
238
+ def json_transform(state, depth)
239
+ delim = ','
240
+ delim << state.array_nl if state
241
+ result = '['
242
+ result << state.array_nl if state
243
+ result << map { |value|
244
+ json_shift(state, depth + 1) << value.to_json(state, depth + 1)
245
+ }.join(delim)
246
+ result << state.array_nl if state
247
+ result << json_shift(state, depth)
248
+ result << ']'
249
+ result
250
+ end
251
+ end
252
+
253
+ module Integer
254
+ # Returns a JSON string representation for this Integer number.
255
+ def to_json(*) to_s end
256
+ end
257
+
258
+ module Float
259
+ # Returns a JSON string representation for this Float number.
260
+ def to_json(*) to_s end
261
+ end
262
+
263
+ module String
264
+ # This string should be encoded with UTF-8 A call to this method
265
+ # returns a JSON string encoded with UTF16 big endian characters as
266
+ # \u????.
267
+ def to_json(*)
268
+ '"' << JSON.utf8_to_json(self) << '"'
269
+ end
270
+
271
+ # Module that holds the extinding methods if, the String module is
272
+ # included.
273
+ module Extend
274
+ # Raw Strings are JSON Objects (the raw bytes are stored in an array for the
275
+ # key "raw"). The Ruby String can be created by this module method.
276
+ def json_create(o)
277
+ o['raw'].pack('C*')
278
+ end
279
+ end
280
+
281
+ # Extends _modul_ with the String::Extend module.
282
+ def self.included(modul)
283
+ modul.extend Extend
284
+ end
285
+
286
+ # This method creates a raw object hash, that can be nested into
287
+ # other data structures and will be unparsed as a raw string. This
288
+ # method should be used, if you want to convert raw strings to JSON
289
+ # instead of UTF-8 strings, e. g. binary data.
290
+ def to_json_raw_object
291
+ {
292
+ JSON.create_id => self.class.name,
293
+ 'raw' => self.unpack('C*'),
294
+ }
295
+ end
296
+
297
+ # This method creates a JSON text from the result of
298
+ # a call to to_json_raw_object of this String.
299
+ def to_json_raw(*args)
300
+ to_json_raw_object.to_json(*args)
301
+ end
302
+ end
303
+
304
+ module TrueClass
305
+ # Returns a JSON string for true: 'true'.
306
+ def to_json(*) 'true' end
307
+ end
308
+
309
+ module FalseClass
310
+ # Returns a JSON string for false: 'false'.
311
+ def to_json(*) 'false' end
312
+ end
313
+
314
+ module NilClass
315
+ # Returns a JSON string for nil: 'null'.
316
+ def to_json(*) 'null' end
317
+ end
318
+ end
319
+ end
320
+ end
321
+ end
@@ -0,0 +1,214 @@
1
+ require 'strscan'
2
+
3
+ module JSON
4
+ module Pure
5
+ # This class implements the JSON parser that is used to parse a JSON string
6
+ # into a Ruby data structure.
7
+ class Parser < StringScanner
8
+ STRING = /" ((?:[^\x0-\x1f"\\] |
9
+ \\["\\\/bfnrt] |
10
+ \\u[0-9a-fA-F]{4})*)
11
+ "/x
12
+ INTEGER = /(-?0|-?[1-9]\d*)/
13
+ FLOAT = /(-?
14
+ (?:0|[1-9]\d*)
15
+ (?:
16
+ \.\d+(?i:e[+-]?\d+) |
17
+ \.\d+ |
18
+ (?i:e[+-]?\d+)
19
+ )
20
+ )/x
21
+ OBJECT_OPEN = /\{/
22
+ OBJECT_CLOSE = /\}/
23
+ ARRAY_OPEN = /\[/
24
+ ARRAY_CLOSE = /\]/
25
+ PAIR_DELIMITER = /:/
26
+ COLLECTION_DELIMITER = /,/
27
+ TRUE = /true/
28
+ FALSE = /false/
29
+ NULL = /null/
30
+ IGNORE = %r(
31
+ (?:
32
+ //[^\n\r]*[\n\r]| # line comments
33
+ /\* # c-style comments
34
+ (?:
35
+ [^*/]| # normal chars
36
+ /[^*]| # slashes that do not start a nested comment
37
+ \*[^/]| # asterisks that do not end this comment
38
+ /(?=\*/) # single slash before this comment's end
39
+ )*
40
+ \*/ # the End of this comment
41
+ |[ \t\r\n]+ # whitespaces: space, horicontal tab, lf, cr
42
+ )+
43
+ )mx
44
+
45
+ UNPARSED = Object.new
46
+
47
+ # Creates a new JSON::Pure::Parser instance for the string _source_.
48
+ def initialize(source)
49
+ super
50
+ @create_id = JSON.create_id
51
+ end
52
+
53
+ alias source string
54
+
55
+ # Parses the current JSON string _source_ and returns the complete data
56
+ # structure as a result.
57
+ def parse
58
+ reset
59
+ obj = nil
60
+ until eos?
61
+ case
62
+ when scan(OBJECT_OPEN)
63
+ obj and raise ParserError, "source '#{peek(20)}' not in JSON!"
64
+ obj = parse_object
65
+ when scan(ARRAY_OPEN)
66
+ obj and raise ParserError, "source '#{peek(20)}' not in JSON!"
67
+ obj = parse_array
68
+ when skip(IGNORE)
69
+ ;
70
+ else
71
+ raise ParserError, "source '#{peek(20)}' not in JSON!"
72
+ end
73
+ end
74
+ obj or raise ParserError, "source did not contain any JSON!"
75
+ obj
76
+ end
77
+
78
+ private
79
+
80
+ # Unescape characters in strings.
81
+ UNESCAPE_MAP = {
82
+ ?" => '"',
83
+ ?\\ => '\\',
84
+ ?/ => '/',
85
+ ?b => "\b",
86
+ ?f => "\f",
87
+ ?n => "\n",
88
+ ?r => "\r",
89
+ ?t => "\t",
90
+ }
91
+
92
+ def parse_string
93
+ if scan(STRING)
94
+ return '' if self[1].empty?
95
+ self[1].gsub(%r((?:\\[\\bfnrt"/]|(?:\\u(?:[A-Fa-f\d]{4}))+))) do |c|
96
+ if u = UNESCAPE_MAP[c[1]]
97
+ u
98
+ else # \uXXXX
99
+ bytes = ''
100
+ i = 0
101
+ while c[6 * i] == ?\\ && c[6 * i + 1] == ?u
102
+ bytes << c[6 * i + 2, 2].to_i(16) << c[6 * i + 4, 2].to_i(16)
103
+ i += 1
104
+ end
105
+ JSON::UTF16toUTF8.iconv(bytes)
106
+ end
107
+ end
108
+ else
109
+ UNPARSED
110
+ end
111
+ rescue Iconv::Failure => e
112
+ raise GeneratorError, "Caught #{e.class}: #{e}"
113
+ end
114
+
115
+ def parse_value
116
+ case
117
+ when scan(FLOAT)
118
+ Float(self[1])
119
+ when scan(INTEGER)
120
+ Integer(self[1])
121
+ when scan(TRUE)
122
+ true
123
+ when scan(FALSE)
124
+ false
125
+ when scan(NULL)
126
+ nil
127
+ when (string = parse_string) != UNPARSED
128
+ string
129
+ when scan(ARRAY_OPEN)
130
+ parse_array
131
+ when scan(OBJECT_OPEN)
132
+ parse_object
133
+ else
134
+ UNPARSED
135
+ end
136
+ end
137
+
138
+ def parse_array
139
+ result = []
140
+ delim = false
141
+ until eos?
142
+ case
143
+ when (value = parse_value) != UNPARSED
144
+ delim = false
145
+ result << value
146
+ skip(IGNORE)
147
+ if scan(COLLECTION_DELIMITER)
148
+ delim = true
149
+ elsif match?(ARRAY_CLOSE)
150
+ ;
151
+ else
152
+ raise ParserError, "expected ',' or ']' in array at '#{peek(20)}'!"
153
+ end
154
+ when scan(ARRAY_CLOSE)
155
+ if delim
156
+ raise ParserError, "expected next element in array at '#{peek(20)}'!"
157
+ end
158
+ break
159
+ when skip(IGNORE)
160
+ ;
161
+ else
162
+ raise ParserError, "unexpected token in array at '#{peek(20)}'!"
163
+ end
164
+ end
165
+ result
166
+ end
167
+
168
+ def parse_object
169
+ result = {}
170
+ delim = false
171
+ until eos?
172
+ case
173
+ when (string = parse_string) != UNPARSED
174
+ skip(IGNORE)
175
+ unless scan(PAIR_DELIMITER)
176
+ raise ParserError, "expected ':' in object at '#{peek(20)}'!"
177
+ end
178
+ skip(IGNORE)
179
+ unless (value = parse_value).equal? UNPARSED
180
+ result[string] = value
181
+ delim = false
182
+ skip(IGNORE)
183
+ if scan(COLLECTION_DELIMITER)
184
+ delim = true
185
+ elsif match?(OBJECT_CLOSE)
186
+ ;
187
+ else
188
+ raise ParserError, "expected ',' or '}' in object at '#{peek(20)}'!"
189
+ end
190
+ else
191
+ raise ParserError, "expected value in object at '#{peek(20)}'!"
192
+ end
193
+ when scan(OBJECT_CLOSE)
194
+ if delim
195
+ raise ParserError, "expected next name, value pair in object at '#{peek(20)}'!"
196
+ end
197
+ if klassname = result[@create_id]
198
+ klass = JSON.deep_const_get klassname
199
+ break unless klass and klass.json_creatable?
200
+ result = klass.json_create(result)
201
+ result
202
+ end
203
+ break
204
+ when skip(IGNORE)
205
+ ;
206
+ else
207
+ raise ParserError, "unexpected token in object at '#{peek(20)}'!"
208
+ end
209
+ end
210
+ result
211
+ end
212
+ end
213
+ end
214
+ end