json-stream 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|