json-stream 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6d404ae0a1e8e03bb1551ead94eda3e413c1f492
4
- data.tar.gz: 0f14c4b8a6de9f53b4bddaf824a0c0184f969399
3
+ metadata.gz: 15b32baaa4333e97f2eae71f21a03af04e8275ea
4
+ data.tar.gz: ea3b178e62a46ae093e5d362abd554fa3c361241
5
5
  SHA512:
6
- metadata.gz: 3640958bef5a32726c09c723a459d15986564bc7afe70bfc0db9faa15037bddd6539fa04a8d79bb95674b56a08901771f33118d3c43e862cc51984d9eca12b39
7
- data.tar.gz: 260eac9e6ff3b440fce89231e0f013bb490ed5666ace3e2c5ee28c3d17cd2eb36a589a5340c42862cf4317a9737e80a976e3ce805b4979ed9dc1df3e9b39ae70
6
+ metadata.gz: c88c6208cdb0597e6ba9ad1d9a86d62605f2329176eabfc15eb43f227a61efae3c1ff66cd1efe340e73245258e325076b6ecdacf6a3f5b870d03ec03d35c986c
7
+ data.tar.gz: cb403d67b89ec3f609b5b9e61b9f12c6a93d654206dbed37b5e9d20a8b94ecd1bd717bfb7f72bf129e3ee923904c4ac033fc90da569879c28e9140b5aa6103bd
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010-2013 David Graham
1
+ Copyright (c) 2010-2014 David Graham
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -65,25 +65,28 @@ def receive_data(data)
65
65
  end
66
66
  ```
67
67
 
68
- Notice how the parser accepts chunks of the JSON document and parses up
69
- to the end of the available buffer. Passing in more data resumes the
70
- parse from the prior state. When an interesting state change happens, the
71
- parser notifies all registered callback procs of the event.
68
+ The parser accepts chunks of the JSON document and parses up to the end of the
69
+ available buffer. Passing in more data resumes the parse from the prior state.
70
+ When an interesting state change happens, the parser notifies all registered
71
+ callback procs of the event.
72
72
 
73
73
  The event callback is where we can do interesting data filtering and passing
74
74
  to other processes. The above example simply prints state changes, but
75
- imagine the callbacks looking for an array named "rows" and processing sets
75
+ imagine the callbacks looking for an array named `rows` and processing sets
76
76
  of these row objects in small batches. Millions of rows, streaming over the
77
77
  network, can be processed in constant memory space this way.
78
78
 
79
79
  ## Dependencies
80
80
 
81
81
  * ruby >= 1.9.2
82
+ * jruby >= 1.7
82
83
 
83
- ## Contact
84
+ ## Alternatives
84
85
 
85
- Project contact: David Graham <david.malcom.graham@gmail.com>
86
+ * [json](https://github.com/flori/json)
87
+ * [yajl-ruby](https://github.com/brianmario/yajl-ruby)
88
+ * [yajl-ffi](https://github.com/dgraham/yajl-ffi)
86
89
 
87
90
  ## License
88
91
 
89
- JSON::Stream is released under the MIT license. Check the LICENSE file for details.
92
+ JSON::Stream is released under the MIT license. Check the LICENSE file for details.
data/Rakefile CHANGED
@@ -12,8 +12,8 @@ task :build => [:pkg] do
12
12
  end
13
13
 
14
14
  Rake::TestTask.new(:test) do |test|
15
- test.libs << 'test'
16
- test.pattern = 'test/**/*_test.rb'
15
+ test.libs << 'spec'
16
+ test.pattern = 'spec/**/*_spec.rb'
17
17
  test.warning = true
18
18
  end
19
19
 
@@ -12,9 +12,9 @@ Gem::Specification.new do |s|
12
12
  s.license = 'MIT'
13
13
 
14
14
  s.files = Dir['[A-Z]*', 'json-stream.gemspec', '{lib}/**/*']
15
- s.test_files = Dir['test/**/*']
15
+ s.test_files = Dir['spec/**/*']
16
16
  s.require_path = 'lib'
17
17
 
18
- s.add_development_dependency 'rake'
18
+ s.add_development_dependency 'rake', '~> 10.3'
19
19
  s.required_ruby_version = '>= 1.9.2'
20
20
  end
@@ -2,42 +2,54 @@
2
2
 
3
3
  module JSON
4
4
  module Stream
5
-
6
5
  # A character buffer that expects a UTF-8 encoded stream of bytes.
7
6
  # This handles truncated multi-byte characters properly so we can just
8
7
  # feed it binary data and receive a properly formatted UTF-8 String as
9
- # output. See here for UTF-8 parsing details:
10
- # http://en.wikipedia.org/wiki/UTF-8
11
- # http://tools.ietf.org/html/rfc3629#section-3
8
+ # output.
9
+ #
10
+ # More UTF-8 parsing details are available at:
11
+ #
12
+ # http://en.wikipedia.org/wiki/UTF-8
13
+ # http://tools.ietf.org/html/rfc3629#section-3
12
14
  class Buffer
13
15
  def initialize
14
- @state, @buf, @need = :start, [], 0
16
+ @state = :start
17
+ @buf = []
18
+ @need = 0
15
19
  end
16
20
 
17
21
  # Fill the buffer with a String of binary UTF-8 encoded bytes. Returns
18
22
  # as much of the data in a UTF-8 String as we have. Truncated multi-byte
19
23
  # characters are saved in the buffer until the next call to this method
20
24
  # where we expect to receive the rest of the multi-byte character.
25
+ #
26
+ # data - The partial binary encoded String data.
27
+ #
28
+ # Raises JSON::Stream::ParserError if the UTF-8 byte sequence is malformed.
29
+ #
30
+ # Returns a UTF-8 encoded String.
21
31
  def <<(data)
22
32
  bytes = []
23
- data.bytes.each do |b|
33
+ data.each_byte do |byte|
24
34
  case @state
25
35
  when :start
26
- if b < 128
27
- bytes << b
28
- elsif b >= 192
36
+ if byte < 128
37
+ bytes << byte
38
+ elsif byte >= 192
29
39
  @state = :multi_byte
30
- @buf << b
31
- @need = case
32
- when b >= 240 then 4
33
- when b >= 224 then 3
34
- when b >= 192 then 2 end
40
+ @buf << byte
41
+ @need =
42
+ case
43
+ when byte >= 240 then 4
44
+ when byte >= 224 then 3
45
+ when byte >= 192 then 2
46
+ end
35
47
  else
36
48
  error('Expected start of multi-byte or single byte char')
37
49
  end
38
50
  when :multi_byte
39
- if b > 127 && b < 192
40
- @buf << b
51
+ if byte > 127 && byte < 192
52
+ @buf << byte
41
53
  if @buf.size == @need
42
54
  bytes += @buf.slice!(0, @buf.size)
43
55
  @state = :start
@@ -52,6 +64,27 @@ module JSON
52
64
  end
53
65
  end
54
66
 
67
+ # Determine if the buffer contains partial UTF-8 continuation bytes that
68
+ # are waiting on subsequent completion bytes before a full codepoint is
69
+ # formed.
70
+ #
71
+ # Examples
72
+ #
73
+ # bytes = "é".bytes
74
+ #
75
+ # buffer << bytes[0]
76
+ # buffer.empty?
77
+ # # => false
78
+ #
79
+ # buffer << bytes[1]
80
+ # buffer.empty?
81
+ # # => true
82
+ #
83
+ # Returns true if the buffer is empty.
84
+ def empty?
85
+ @buf.empty?
86
+ end
87
+
55
88
  private
56
89
 
57
90
  def error(message)
@@ -2,17 +2,15 @@
2
2
 
3
3
  module JSON
4
4
  module Stream
5
- # A parser listener that builds a full, in memory, object graph from a
6
- # JSON document. Typically, we would use the json gem's JSON.parse() method
7
- # when we have the full JSON document because it's much faster than this.
8
- # JSON::Stream is typically used when we have a huge JSON document streaming
9
- # to us and we don't want to hold the entire parsed object in memory.
10
- # Regardless, this is a good example of how to write parser callbacks.
5
+ # A parser listener that builds a full, in memory, object from a JSON
6
+ # document. This is similar to using the json gem's `JSON.parse` method.
11
7
  #
12
- # parser = JSON::Stream::Parser.new
13
- # builder = JSON::Stream::Builder.new(parser)
14
- # parser << json
15
- # obj = builder.result
8
+ # Examples
9
+ #
10
+ # parser = JSON::Stream::Parser.new
11
+ # builder = JSON::Stream::Builder.new(parser)
12
+ # parser << '{"answer": 42, "question": false}'
13
+ # obj = builder.result
16
14
  class Builder
17
15
  METHODS = %w[start_document end_document start_object end_object start_array end_array key value]
18
16
 
@@ -25,66 +23,49 @@ module JSON
25
23
  end
26
24
 
27
25
  def start_document
28
- @stack, @result = [], nil
26
+ @stack = []
27
+ @keys = []
28
+ @result = nil
29
29
  end
30
30
 
31
31
  def end_document
32
- @result = @stack.pop.obj
32
+ @result = @stack.pop
33
33
  end
34
34
 
35
35
  def start_object
36
- @stack.push(ObjectNode.new)
36
+ @stack.push({})
37
37
  end
38
38
 
39
39
  def end_object
40
- unless @stack.size == 1
41
- node = @stack.pop
42
- @stack[-1] << node.obj
40
+ return if @stack.size == 1
41
+ node = @stack.pop
42
+
43
+ case @stack.last
44
+ when Hash
45
+ @stack.last[@keys.pop] = node
46
+ when Array
47
+ @stack.last << node
43
48
  end
44
49
  end
45
50
  alias :end_array :end_object
46
51
 
47
52
  def start_array
48
- @stack.push(ArrayNode.new)
53
+ @stack.push([])
49
54
  end
50
55
 
51
56
  def key(key)
52
- @stack[-1] << key
57
+ @keys << key
53
58
  end
54
59
 
55
60
  def value(value)
56
- @stack[-1] << value
57
- end
58
- end
59
-
60
- class ArrayNode
61
- attr_reader :obj
62
-
63
- def initialize
64
- @obj = []
65
- end
66
-
67
- def <<(node)
68
- @obj << node
69
- self
70
- end
71
- end
72
-
73
- class ObjectNode
74
- attr_reader :obj
75
-
76
- def initialize
77
- @obj, @key = {}, nil
78
- end
79
-
80
- def <<(node)
81
- if @key
82
- @obj[@key] = node
83
- @key = nil
61
+ case @stack.last
62
+ when Hash
63
+ @stack.last[@keys.pop] = value
64
+ when Array
65
+ @stack.last << value
84
66
  else
85
- @key = node
67
+ @stack << value
86
68
  end
87
- self
88
69
  end
89
70
  end
90
71
  end
@@ -2,15 +2,24 @@
2
2
 
3
3
  module JSON
4
4
  module Stream
5
- class ParserError < RuntimeError; end
5
+ # Raised on any invalid JSON text.
6
+ ParserError = Class.new(RuntimeError)
6
7
 
7
- # A streaming JSON parser that generates SAX-like events for
8
- # state changes. Use the json gem for small documents. Use this
9
- # for huge documents that won't fit in memory.
8
+ # A streaming JSON parser that generates SAX-like events for state changes.
9
+ # Use the json gem for small documents. Use this for huge documents that
10
+ # won't fit in memory.
11
+ #
12
+ # Examples
13
+ #
14
+ # parser = JSON::Stream::Parser.new
15
+ # parser.key {|key| puts key }
16
+ # parser.value {|value| puts value }
17
+ # parser << '{"answer":'
18
+ # parser << ' 42}'
10
19
  class Parser
11
- BUF_SIZE = 512
20
+ BUF_SIZE = 4096
12
21
  CONTROL = /[\x00-\x1F]/
13
- WS = /\s/
22
+ WS = /[ \n\t\r]/
14
23
  HEX = /[0-9a-fA-F]/
15
24
  DIGIT = /[0-9]/
16
25
  DIGIT_1_9 = /[1-9]/
@@ -40,6 +49,17 @@ module JSON
40
49
  # Parses a full JSON document from a String or an IO stream and returns
41
50
  # the parsed object graph. For parsing small JSON documents with small
42
51
  # memory requirements, use the json gem's faster JSON.parse method instead.
52
+ #
53
+ # json - The String or IO containing JSON data.
54
+ #
55
+ # Examples
56
+ #
57
+ # JSON::Stream::Parser.parse('{"hello": "world"}')
58
+ # # => {"hello": "world"}
59
+ #
60
+ # Raises a JSON::Stream::ParserError if the JSON data is malformed.
61
+ #
62
+ # Returns a Hash.
43
63
  def self.parse(json)
44
64
  stream = json.is_a?(String) ? StringIO.new(json) : json
45
65
  parser = Parser.new
@@ -47,71 +67,138 @@ module JSON
47
67
  while (buf = stream.read(BUF_SIZE)) != nil
48
68
  parser << buf
49
69
  end
50
- raise ParserError, "unexpected eof" unless builder.result
70
+ parser.finish
51
71
  builder.result
52
72
  ensure
53
73
  stream.close
54
74
  end
55
75
 
56
- %w[start_document end_document start_object end_object
57
- start_array end_array key value].each do |name|
76
+ # Drain any remaining buffered characters into the parser to complete
77
+ # the parsing of the document.
78
+ #
79
+ # This is only required when parsing a document containing a single
80
+ # numeric value, integer or float. The parser has no other way to
81
+ # detect when it should no longer expect additional characters with
82
+ # which to complete the parse, so it must be signaled by a call to
83
+ # this method.
84
+ #
85
+ # If you're parsing more typical object or array documents, there's no
86
+ # need to call `finish` because the parse will complete when the final
87
+ # closing `]` or `}` character is scanned.
88
+ #
89
+ # Raises a JSON::Stream::ParserError if the JSON data is malformed.
90
+ #
91
+ # Returns nothing.
92
+ def finish
93
+ # Partial multi-byte character waiting for completion bytes.
94
+ error('Unexpected end-of-file') unless @utf8.empty?
58
95
 
59
- define_method(name) do |&block|
60
- @listeners[name] << block
61
- end
96
+ # Partial array, object, or string.
97
+ error('Unexpected end-of-file') unless @stack.empty?
62
98
 
63
- define_method("notify_#{name}") do |*args|
64
- @listeners[name].each do |block|
65
- block.call(*args)
66
- end
99
+ case @state
100
+ when :end_document
101
+ # done, do nothing
102
+ when :in_float
103
+ end_value(@buf.to_f)
104
+ when :in_exponent
105
+ error('Unexpected end-of-file') unless @buf =~ DIGIT_END
106
+ end_value(@buf.to_f)
107
+ when :start_zero
108
+ end_value(@buf.to_i)
109
+ when :start_int
110
+ end_value(@buf.to_i)
111
+ else
112
+ error('Unexpected end-of-file')
67
113
  end
68
- private "notify_#{name}"
69
114
  end
70
115
 
71
116
  # Create a new parser with an optional initialization block where
72
- # we can register event callbacks. For example:
73
- # parser = JSON::Stream::Parser.new do
74
- # start_document { puts "start document" }
75
- # end_document { puts "end document" }
76
- # start_object { puts "start object" }
77
- # end_object { puts "end object" }
78
- # start_array { puts "start array" }
79
- # end_array { puts "end array" }
80
- # key {|k| puts "key: #{k}" }
81
- # value {|v| puts "value: #{v}" }
82
- # end
117
+ # we can register event callbacks.
118
+ #
119
+ # Examples
120
+ #
121
+ # parser = JSON::Stream::Parser.new do
122
+ # start_document { puts "start document" }
123
+ # end_document { puts "end document" }
124
+ # start_object { puts "start object" }
125
+ # end_object { puts "end object" }
126
+ # start_array { puts "start array" }
127
+ # end_array { puts "end array" }
128
+ # key {|k| puts "key: #{k}" }
129
+ # value {|v| puts "value: #{v}" }
130
+ # end
83
131
  def initialize(&block)
84
132
  @state = :start_document
85
133
  @utf8 = Buffer.new
86
- @listeners = Hash.new {|h, k| h[k] = [] }
87
- @stack, @unicode, @buf, @pos = [], "", "", -1
134
+ @listeners = {
135
+ start_document: [],
136
+ end_document: [],
137
+ start_object: [],
138
+ end_object: [],
139
+ start_array: [],
140
+ end_array: [],
141
+ key: [],
142
+ value: []
143
+ }
144
+
145
+ # Track parse stack.
146
+ @stack = []
147
+ @unicode = ""
148
+ @buf = ""
149
+ @pos = -1
150
+
151
+ # Register any observers in the block.
88
152
  instance_eval(&block) if block_given?
89
153
  end
90
154
 
155
+ def start_document(&block)
156
+ @listeners[:start_document] << block
157
+ end
158
+
159
+ def end_document(&block)
160
+ @listeners[:end_document] << block
161
+ end
162
+
163
+ def start_object(&block)
164
+ @listeners[:start_object] << block
165
+ end
166
+
167
+ def end_object(&block)
168
+ @listeners[:end_object] << block
169
+ end
170
+
171
+ def start_array(&block)
172
+ @listeners[:start_array] << block
173
+ end
174
+
175
+ def end_array(&block)
176
+ @listeners[:end_array] << block
177
+ end
178
+
179
+ def key(&block)
180
+ @listeners[:key] << block
181
+ end
182
+
183
+ def value(&block)
184
+ @listeners[:value] << block
185
+ end
186
+
91
187
  # Pass data into the parser to advance the state machine and
92
188
  # generate callback events. This is well suited for an EventMachine
93
189
  # receive_data loop.
190
+ #
191
+ # data - The String of partial JSON data to parse.
192
+ #
193
+ # Raises a JSON::Stream::ParserError if the JSON data is malformed.
194
+ #
195
+ # Returns nothing.
94
196
  def <<(data)
95
197
  (@utf8 << data).each_char do |ch|
96
198
  @pos += 1
97
199
  case @state
98
200
  when :start_document
99
- case ch
100
- when LEFT_BRACE
101
- @state = :start_object
102
- @stack.push(:object)
103
- notify_start_document
104
- notify_start_object
105
- when LEFT_BRACKET
106
- @state = :start_array
107
- @stack.push(:array)
108
- notify_start_document
109
- notify_start_array
110
- when WS
111
- # ignore
112
- else
113
- error("Expected object or array start")
114
- end
201
+ start_value(ch)
115
202
  when :start_object
116
203
  case ch
117
204
  when RIGHT_BRACE
@@ -122,17 +209,16 @@ module JSON
122
209
  when WS
123
210
  # ignore
124
211
  else
125
- error("Expected object key start")
212
+ error('Expected object key start')
126
213
  end
127
214
  when :start_string
128
215
  case ch
129
216
  when QUOTE
130
217
  if @stack.pop == :string
131
- @state = :end_value
132
- notify_value(@buf)
218
+ end_value(@buf)
133
219
  else # :key
134
220
  @state = :end_key
135
- notify_key(@buf)
221
+ notify(:key, @buf)
136
222
  end
137
223
  @buf = ""
138
224
  when BACKSLASH
@@ -165,7 +251,7 @@ module JSON
165
251
  when U
166
252
  @state = :unicode_escape
167
253
  else
168
- error("Expected escaped character")
254
+ error('Expected escaped character')
169
255
  end
170
256
  when :unicode_escape
171
257
  case ch
@@ -174,7 +260,7 @@ module JSON
174
260
  if @unicode.size == 4
175
261
  codepoint = @unicode.slice!(0, 4).hex
176
262
  if codepoint >= 0xD800 && codepoint <= 0xDBFF
177
- error('Expected low surrogate pair half') if @stack[-1].is_a?(Fixnum)
263
+ error('Expected low surrogate pair half') if @stack.last.is_a?(Fixnum)
178
264
  @state = :start_surrogate_pair
179
265
  @stack.push(codepoint)
180
266
  elsif codepoint >= 0xDC00 && codepoint <= 0xDFFF
@@ -225,8 +311,7 @@ module JSON
225
311
  @state = :start_exponent
226
312
  @buf << ch
227
313
  else
228
- @state = :end_value
229
- notify_value(@buf.to_i)
314
+ end_value(@buf.to_i)
230
315
  @buf = ""
231
316
  @pos -= 1
232
317
  redo
@@ -247,8 +332,7 @@ module JSON
247
332
  @state = :start_exponent
248
333
  @buf << ch
249
334
  else
250
- @state = :end_value
251
- notify_value(@buf.to_f)
335
+ end_value(@buf.to_f)
252
336
  @buf = ""
253
337
  @pos -= 1
254
338
  redo
@@ -267,9 +351,7 @@ module JSON
267
351
  @buf << ch
268
352
  else
269
353
  error('Expected 0-9 digit') unless @buf =~ DIGIT_END
270
- @state = :end_value
271
- num = @buf.include?('.') ? @buf.to_f : @buf.to_i
272
- notify_value(num)
354
+ end_value(@buf.to_f)
273
355
  @buf = ""
274
356
  @pos -= 1
275
357
  redo
@@ -285,8 +367,7 @@ module JSON
285
367
  @state = :start_exponent
286
368
  @buf << ch
287
369
  else
288
- @state = :end_value
289
- notify_value(@buf.to_i)
370
+ end_value(@buf.to_i)
290
371
  @buf = ""
291
372
  @pos -= 1
292
373
  redo
@@ -304,7 +385,7 @@ module JSON
304
385
  when WS
305
386
  # ignore
306
387
  else
307
- error("Expected colon key separator")
388
+ error('Expected colon key separator')
308
389
  end
309
390
  when :key_sep
310
391
  start_value(ch)
@@ -328,10 +409,10 @@ module JSON
328
409
  when WS
329
410
  # ignore
330
411
  else
331
- error("Expected comma or object or array close")
412
+ error('Expected comma or object or array close')
332
413
  end
333
414
  when :value_sep
334
- if @stack[-1] == :object
415
+ if @stack.last == :object
335
416
  case ch
336
417
  when QUOTE
337
418
  @state = :start_string
@@ -339,59 +420,120 @@ module JSON
339
420
  when WS
340
421
  # ignore
341
422
  else
342
- error("Expected object key start")
423
+ error('Expected object key start')
343
424
  end
344
425
  else
345
426
  start_value(ch)
346
427
  end
347
428
  when :end_document
348
- error("Unexpected data") unless ch =~ WS
429
+ error('Unexpected data') unless ch =~ WS
349
430
  end
350
431
  end
351
432
  end
352
433
 
353
434
  private
354
435
 
436
+ # Invoke all registered observer procs for the event type.
437
+ #
438
+ # type - The Symbol listener name.
439
+ # args - The argument list to pass into the observer procs.
440
+ #
441
+ # Examples
442
+ #
443
+ # # broadcast events for {"answer": 42}
444
+ # notify(:start_object)
445
+ # notify(:key, "answer")
446
+ # notify(:value, 42)
447
+ # notify(:end_object)
448
+ #
449
+ # Returns nothing.
450
+ def notify(type, *args)
451
+ @listeners[type].each do |block|
452
+ block.call(*args)
453
+ end
454
+ end
455
+
456
+ # Complete an object or array container value type.
457
+ #
458
+ # type - The Symbol, :object or :array, of the expected type.
459
+ #
460
+ # Raises a JSON::Stream::ParserError if the expected container type
461
+ # was not completed.
462
+ #
463
+ # Returns nothing.
355
464
  def end_container(type)
356
465
  @state = :end_value
357
466
  if @stack.pop == type
358
- send("notify_end_#{type}")
467
+ case type
468
+ when :object then notify(:end_object)
469
+ when :array then notify(:end_array)
470
+ end
359
471
  else
360
472
  error("Expected end of #{type}")
361
473
  end
362
- if @stack.empty?
363
- @state = :end_document
364
- notify_end_document
365
- end
474
+ notify_end_document if @stack.empty?
475
+ end
476
+
477
+ # Broadcast an `end_document` event to observers after a complete JSON
478
+ # value document (object, array, number, string, true, false, null) has
479
+ # been parsed from the text. This is the final event sent to observers
480
+ # and signals the parse has finished.
481
+ #
482
+ # Returns nothing.
483
+ def notify_end_document
484
+ @state = :end_document
485
+ notify(:end_document)
366
486
  end
367
487
 
488
+ # Parse one of the three allowed keywords: true, false, null.
489
+ #
490
+ # word - The String keyword ('true', 'false', 'null').
491
+ # value - The Ruby value (true, false, nil).
492
+ # re - The Regexp of allowed keyword characters.
493
+ # ch - The current String character being parsed.
494
+ #
495
+ # Raises a JSON::Stream::ParserError if the character does not belong
496
+ # in the expected keyword.
497
+ #
498
+ # Returns nothing.
368
499
  def keyword(word, value, re, ch)
369
500
  if ch =~ re
370
501
  @buf << ch
371
502
  else
372
503
  error("Expected #{word} keyword")
373
504
  end
505
+
374
506
  if @buf.size == word.size
375
507
  if @buf == word
376
- @state = :end_value
377
508
  @buf = ""
378
- notify_value(value)
509
+ end_value(value)
379
510
  else
380
511
  error("Expected #{word} keyword")
381
512
  end
382
513
  end
383
514
  end
384
515
 
516
+ # Process the first character of one of the seven possible JSON
517
+ # values: object, array, string, true, false, null, number.
518
+ #
519
+ # ch - The current character String.
520
+ #
521
+ # Raises a JSON::Stream::ParserError if the character does not signal
522
+ # the start of a value.
523
+ #
524
+ # Returns nothing.
385
525
  def start_value(ch)
386
526
  case ch
387
527
  when LEFT_BRACE
528
+ notify(:start_document) if @stack.empty?
388
529
  @state = :start_object
389
530
  @stack.push(:object)
390
- notify_start_object
531
+ notify(:start_object)
391
532
  when LEFT_BRACKET
533
+ notify(:start_document) if @stack.empty?
392
534
  @state = :start_array
393
535
  @stack.push(:array)
394
- notify_start_array
536
+ notify(:start_array)
395
537
  when QUOTE
396
538
  @state = :start_string
397
539
  @stack.push(:string)
@@ -416,10 +558,23 @@ module JSON
416
558
  when WS
417
559
  # ignore
418
560
  else
419
- error("Expected value")
561
+ error('Expected value')
420
562
  end
421
563
  end
422
564
 
565
+ # Advance the state machine and notify `value` observers that a
566
+ # string, number or keyword (true, false, null) value was parsed.
567
+ #
568
+ # value - The object to broadcast to observers.
569
+ #
570
+ # Returns nothing.
571
+ def end_value(value)
572
+ @state = :end_value
573
+ notify(:start_document) if @stack.empty?
574
+ notify(:value, value)
575
+ notify_end_document if @stack.empty?
576
+ end
577
+
423
578
  def error(message)
424
579
  raise ParserError, "#{message}: char #{@pos}"
425
580
  end