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 +4 -4
- data/LICENSE +1 -1
- data/README.md +11 -8
- data/Rakefile +2 -2
- data/json-stream.gemspec +2 -2
- data/lib/json/stream/buffer.rb +49 -16
- data/lib/json/stream/builder.rb +29 -48
- data/lib/json/stream/parser.rb +231 -76
- data/lib/json/stream/version.rb +1 -1
- data/spec/buffer_spec.rb +103 -0
- data/spec/builder_spec.rb +157 -0
- data/spec/fixtures/repository.json +107 -0
- data/spec/parser_spec.rb +913 -0
- metadata +19 -17
- data/test/buffer_test.rb +0 -87
- data/test/builder_test.rb +0 -123
- data/test/parser_test.rb +0 -453
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 15b32baaa4333e97f2eae71f21a03af04e8275ea
|
4
|
+
data.tar.gz: ea3b178e62a46ae093e5d362abd554fa3c361241
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c88c6208cdb0597e6ba9ad1d9a86d62605f2329176eabfc15eb43f227a61efae3c1ff66cd1efe340e73245258e325076b6ecdacf6a3f5b870d03ec03d35c986c
|
7
|
+
data.tar.gz: cb403d67b89ec3f609b5b9e61b9f12c6a93d654206dbed37b5e9d20a8b94ecd1bd717bfb7f72bf129e3ee923904c4ac033fc90da569879c28e9140b5aa6103bd
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -65,25 +65,28 @@ def receive_data(data)
|
|
65
65
|
end
|
66
66
|
```
|
67
67
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|
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
|
-
##
|
84
|
+
## Alternatives
|
84
85
|
|
85
|
-
|
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.
|
92
|
+
JSON::Stream is released under the MIT license. Check the LICENSE file for details.
|
data/Rakefile
CHANGED
data/json-stream.gemspec
CHANGED
@@ -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['
|
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
|
data/lib/json/stream/buffer.rb
CHANGED
@@ -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.
|
10
|
-
#
|
11
|
-
#
|
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
|
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.
|
33
|
+
data.each_byte do |byte|
|
24
34
|
case @state
|
25
35
|
when :start
|
26
|
-
if
|
27
|
-
bytes <<
|
28
|
-
elsif
|
36
|
+
if byte < 128
|
37
|
+
bytes << byte
|
38
|
+
elsif byte >= 192
|
29
39
|
@state = :multi_byte
|
30
|
-
@buf <<
|
31
|
-
@need =
|
32
|
-
|
33
|
-
when
|
34
|
-
when
|
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
|
40
|
-
@buf <<
|
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)
|
data/lib/json/stream/builder.rb
CHANGED
@@ -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
|
6
|
-
#
|
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
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
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
|
26
|
+
@stack = []
|
27
|
+
@keys = []
|
28
|
+
@result = nil
|
29
29
|
end
|
30
30
|
|
31
31
|
def end_document
|
32
|
-
@result = @stack.pop
|
32
|
+
@result = @stack.pop
|
33
33
|
end
|
34
34
|
|
35
35
|
def start_object
|
36
|
-
@stack.push(
|
36
|
+
@stack.push({})
|
37
37
|
end
|
38
38
|
|
39
39
|
def end_object
|
40
|
-
|
41
|
-
|
42
|
-
|
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(
|
53
|
+
@stack.push([])
|
49
54
|
end
|
50
55
|
|
51
56
|
def key(key)
|
52
|
-
@
|
57
|
+
@keys << key
|
53
58
|
end
|
54
59
|
|
55
60
|
def value(value)
|
56
|
-
@stack
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
@
|
67
|
+
@stack << value
|
86
68
|
end
|
87
|
-
self
|
88
69
|
end
|
89
70
|
end
|
90
71
|
end
|
data/lib/json/stream/parser.rb
CHANGED
@@ -2,15 +2,24 @@
|
|
2
2
|
|
3
3
|
module JSON
|
4
4
|
module Stream
|
5
|
-
|
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
|
-
#
|
9
|
-
#
|
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 =
|
20
|
+
BUF_SIZE = 4096
|
12
21
|
CONTROL = /[\x00-\x1F]/
|
13
|
-
WS =
|
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
|
-
|
70
|
+
parser.finish
|
51
71
|
builder.result
|
52
72
|
ensure
|
53
73
|
stream.close
|
54
74
|
end
|
55
75
|
|
56
|
-
|
57
|
-
|
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
|
-
|
60
|
-
|
61
|
-
end
|
96
|
+
# Partial array, object, or string.
|
97
|
+
error('Unexpected end-of-file') unless @stack.empty?
|
62
98
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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.
|
73
|
-
#
|
74
|
-
#
|
75
|
-
#
|
76
|
-
#
|
77
|
-
#
|
78
|
-
#
|
79
|
-
#
|
80
|
-
#
|
81
|
-
#
|
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 =
|
87
|
-
|
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
|
-
|
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(
|
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
|
-
@
|
132
|
-
notify_value(@buf)
|
218
|
+
end_value(@buf)
|
133
219
|
else # :key
|
134
220
|
@state = :end_key
|
135
|
-
|
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(
|
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
|
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
|
-
@
|
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
|
-
@
|
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
|
-
@
|
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
|
-
@
|
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(
|
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(
|
412
|
+
error('Expected comma or object or array close')
|
332
413
|
end
|
333
414
|
when :value_sep
|
334
|
-
if @stack
|
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(
|
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(
|
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
|
-
|
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
|
-
|
364
|
-
|
365
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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(
|
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
|