yajl-ffi 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1b714671d249f07a8fd35a4f13a236a572a57f17
4
+ data.tar.gz: 8449424c54943747b93f9927e5bacc24969099d9
5
+ SHA512:
6
+ metadata.gz: d1c3aaeeb4c9d8af71712bfbe420ec93c3ef9a664f59227eb619f1bc561c926b07ca82953317d015240ff594e61a61bf281d54ce8ba1b6a22829b5d453577bcf
7
+ data.tar.gz: da1ea8c2301ea0020361de9cc56acde0fbe3bf13baeef046123c2d350cfe00a490b1e07f27cf8aa27909aef25c7d5033d3b5315f1a0a5c9e58d18de67bc3ce48
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2014 David Graham
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,180 @@
1
+ # Yajl::FFI
2
+
3
+ Yajl::FFI is a [JSON](http://json.org) parser, based on
4
+ [FFI](https://github.com/ffi/ffi) bindings into the native
5
+ [YAJL](https://github.com/lloyd/yajl) library, that generates
6
+ events for each state change. This allows streaming both the JSON document into
7
+ memory and the parsed object graph out of memory to some other process.
8
+
9
+ This is similar to an XML SAX parser that generates events during parsing. There
10
+ is no requirement for the document, or the object graph, to be fully buffered in
11
+ memory. Yajl::FFI is best suited for huge JSON documents that won't fit in memory.
12
+
13
+ ## Usage
14
+
15
+ The simplest way to parse is to read the full JSON document into memory
16
+ and then parse it into a full object graph. This is fine for small documents
17
+ because we have room for both the text and parsed object in memory.
18
+
19
+ ```ruby
20
+ require 'yajl/ffi'
21
+ json = File.read('/tmp/test.json')
22
+ obj = Yajl::FFI::Parser.parse(json)
23
+ ```
24
+
25
+ While it's possible to do this with Yajl::FFI, we should really use the
26
+ standard library's [json]([json](https://github.com/flori/json)
27
+ gem for documents like this. It's faster because it doesn't need to generate
28
+ events and notify observers each time the parser changes state. It parses and
29
+ builds the Ruby object entirely in native code and hands it back to us, fully
30
+ formed.
31
+
32
+ For larger documents, we can use an IO object to stream it into the parser.
33
+ We still need room for the parsed object, but the document itself is never
34
+ fully read into memory.
35
+
36
+ ```ruby
37
+ require 'yajl/ffi'
38
+ stream = File.open('/tmp/test.json')
39
+ obj = Yajl::FFI::Parser.parse(stream)
40
+ ```
41
+
42
+ However, when streaming small documents from disk, or over the network, the
43
+ [yajl-ruby](https://github.com/brianmario/yajl-ruby) gem will give us the best
44
+ performance.
45
+
46
+ Huge documents arriving over the network in small chunks to an
47
+ [EventMachine](https://github.com/eventmachine/eventmachine)
48
+ `receive_data` loop is where Yajl::FFI is uniquely suited. Inside an
49
+ `EventMachine::Connection` subclass we might have:
50
+
51
+ ```ruby
52
+ def post_init
53
+ @parser = Yajl::FFI::Parser.new
54
+ @parser.start_document { puts "start document" }
55
+ @parser.end_document { puts "end document" }
56
+ @parser.start_object { puts "start object" }
57
+ @parser.end_object { puts "end object" }
58
+ @parser.start_array { puts "start array" }
59
+ @parser.end_array { puts "end array" }
60
+ @parser.key {|k| puts "key: #{k}" }
61
+ @parser.value {|v| puts "value: #{v}" }
62
+ end
63
+
64
+ def receive_data(data)
65
+ begin
66
+ @parser << data
67
+ rescue Yajl::FFI::ParserError => e
68
+ close_connection
69
+ end
70
+ end
71
+ ```
72
+
73
+ The parser accepts chunks of the JSON document and parses up to the end of the
74
+ available buffer. Passing in more data resumes the parse from the prior state.
75
+ When an interesting state change happens, the parser notifies all registered
76
+ callback procs of the event.
77
+
78
+ The event callback is where we can do interesting data filtering and passing
79
+ to other processes. The above example simply prints state changes, but the
80
+ callbacks might look for an array named `rows` and process sets of these row
81
+ objects in small batches. Millions of rows, streaming over the network, can be
82
+ processed in constant memory space this way.
83
+
84
+ ## Dependencies
85
+
86
+ * [libyajl2](https://github.com/lloyd/yajl)
87
+ * ruby >= 1.9.3
88
+ * jruby >= 1.7
89
+
90
+ ## Library loading
91
+
92
+ FFI uses the the `dlopen` system call to dynamically load the libyajl library
93
+ into memory at runtime. It searches the usual directories for the library file,
94
+ like `/usr/lib` and `/usr/local/lib`, and raises an error if it's not found.
95
+ If libyajl is installed in an unusual directory, we can tell `dlopen` where to
96
+ look by setting the `LD_LIBRARY_PATH` environment variable.
97
+
98
+ ```sh
99
+ # test normal library load
100
+ $ ruby -r 'yajl/ffi' -e 'puts Yajl::FFI::VERSION'
101
+
102
+ # if it fails, specify the search path
103
+ $ LD_LIBRARY_PATH=/somewhere/yajl/lib \
104
+ ruby -r 'yajl/ffi' -e 'puts Yajl::FFI::VERSION'
105
+ ```
106
+
107
+ ## Installation
108
+
109
+ The libyajl library needs to be installed before this gem can bind to it.
110
+
111
+ ### OS X
112
+
113
+ Use [Homebrew](http://brew.sh) or compile from source below.
114
+
115
+ ```
116
+ $ brew install yajl
117
+ ```
118
+
119
+ ### Fedora
120
+
121
+ Fedora 20 provides libyajl2 in a package. Older versions might need to compile
122
+ the latest yajl version from source.
123
+
124
+ ```
125
+ $ sudo yum install yajl
126
+ ```
127
+
128
+ ### Ubuntu
129
+
130
+ Ubuntu 14.04 provides a libyajl2 package. Older versions might also need to
131
+ compile yajl from source.
132
+
133
+ ```
134
+ $ sudo apt-get install libyajl2
135
+ ```
136
+
137
+ ### Source
138
+
139
+ By default, this compiles and installs to `/usr/local`. Use
140
+ `./configure -p /tmp/somewhere` to install to a different directory.
141
+ Setting `LD_LIBRARY_PATH` will be required in that case.
142
+
143
+ ```
144
+ $ git clone https://github.com/lloyd/yajl
145
+ $ cd yajl
146
+ $ ./configure
147
+ $ make && make install
148
+ ```
149
+
150
+ ## Alternatives
151
+
152
+ * [json](https://github.com/flori/json)
153
+ * [yajl-ruby](https://github.com/brianmario/yajl-ruby)
154
+ * [json-stream](https://github.com/dgraham/json-stream)
155
+
156
+ This gem provides a benchmark script to test the relative performance of
157
+ several parsers. Here's a sample run.
158
+
159
+ ```
160
+ $ rake benchmark
161
+ user system total real
162
+ json 0.130000 0.010000 0.140000 ( 0.136225)
163
+ yajl-ruby 0.130000 0.010000 0.140000 ( 0.133872)
164
+ yajl-ffi 0.800000 0.010000 0.810000 ( 0.812818)
165
+ json-stream 9.500000 0.080000 9.580000 ( 9.580571)
166
+ ```
167
+
168
+ Yajl::FFI is about 6x slower than the pure native parsers. JSON::Stream is a
169
+ pure Ruby parser, and it performs accordingly. But it's useful in cases where
170
+ you're unable to use native bindings or when the limiting factor is the
171
+ network, rather than processor speed.
172
+
173
+ So if you need to parse many small JSON documents, the json and yajl-ruby gems
174
+ are the best options. If you need to stream, and incrementally parse, pieces of a
175
+ large document in constant memory space, yajl-ffi and json-stream are good
176
+ choices.
177
+
178
+ ## License
179
+
180
+ Yajl::FFI is released under the MIT license. Check the LICENSE file for details.
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ require 'rake'
2
+ require 'rake/clean'
3
+ require 'rake/testtask'
4
+
5
+ load './lib/yajl/ffi/tasks/benchmark.rake'
6
+
7
+ CLOBBER.include('pkg')
8
+
9
+ directory 'pkg'
10
+
11
+ desc 'Build distributable packages'
12
+ task :build => [:pkg] do
13
+ system 'gem build yajl-ffi.gemspec && mv yajl-ffi-*.gem pkg/'
14
+ end
15
+
16
+ Rake::TestTask.new(:test) do |test|
17
+ test.libs << 'test'
18
+ test.pattern = 'spec/**/*_spec.rb'
19
+ test.warning = true
20
+ end
21
+
22
+ task :default => [:clobber, :test, :build]
data/lib/yajl/ffi.rb ADDED
@@ -0,0 +1,54 @@
1
+ require 'ffi'
2
+ require 'stringio'
3
+ require 'yajl/ffi/builder'
4
+ require 'yajl/ffi/parser'
5
+ require 'yajl/ffi/version'
6
+
7
+ module Yajl
8
+ module FFI
9
+ extend ::FFI::Library
10
+
11
+ ffi_lib 'yajl'
12
+
13
+ enum :status, [
14
+ :ok,
15
+ :client_canceled,
16
+ :error
17
+ ]
18
+
19
+ enum :options, [
20
+ :allow_comments, 0x01,
21
+ :allow_invalid_utf8, 0x02,
22
+ :allow_trailing_garbage, 0x04,
23
+ :allow_multiple_values, 0x08,
24
+ :allow_partial_values, 0x10
25
+ ]
26
+
27
+ class Callbacks < ::FFI::Struct
28
+ layout \
29
+ :on_null, :pointer,
30
+ :on_boolean, :pointer,
31
+ :on_integer, :pointer,
32
+ :on_double, :pointer,
33
+ :on_number, :pointer,
34
+ :on_string, :pointer,
35
+ :on_start_object, :pointer,
36
+ :on_key, :pointer,
37
+ :on_end_object, :pointer,
38
+ :on_start_array, :pointer,
39
+ :on_end_array, :pointer
40
+ end
41
+
42
+ typedef :pointer, :handle
43
+
44
+ attach_function :alloc, :yajl_alloc, [:pointer, :pointer, :pointer], :handle
45
+ attach_function :free, :yajl_free, [:handle], :void
46
+ attach_function :config, :yajl_config, [:handle, :options, :varargs], :int
47
+ attach_function :parse, :yajl_parse, [:handle, :pointer, :size_t], :status
48
+ attach_function :complete_parse, :yajl_complete_parse, [:handle], :status
49
+ attach_function :get_error, :yajl_get_error, [:handle, :int, :pointer, :size_t], :pointer
50
+ attach_function :free_error, :yajl_free_error, [:handle, :pointer], :void
51
+ attach_function :get_bytes_consumed, :yajl_get_bytes_consumed, [:handle], :size_t
52
+ attach_function :status_to_string, :yajl_status_to_string, [:status], :string
53
+ end
54
+ end
@@ -0,0 +1,70 @@
1
+ module Yajl
2
+ module FFI
3
+ # A parser listener that builds a full, in memory, object from a JSON
4
+ # document. This is similar to using the json gem's `JSON.parse` method.
5
+ #
6
+ # Examples
7
+ #
8
+ # parser = Yajl::FFI::Parser.new
9
+ # builder = Yajl::FFI::Builder.new(parser)
10
+ # parser << '{"answer": 42, "question": false}'
11
+ # obj = builder.result
12
+ class Builder
13
+ METHODS = %w[start_document end_document start_object end_object start_array end_array key value]
14
+
15
+ attr_reader :result
16
+
17
+ def initialize(parser)
18
+ METHODS.each do |name|
19
+ parser.send(name, &method(name))
20
+ end
21
+ end
22
+
23
+ def start_document
24
+ @stack = []
25
+ @keys = []
26
+ @result = nil
27
+ end
28
+
29
+ def end_document
30
+ @result = @stack.pop
31
+ end
32
+
33
+ def start_object
34
+ @stack.push({})
35
+ end
36
+
37
+ def end_object
38
+ return if @stack.size == 1
39
+ node = @stack.pop
40
+
41
+ case @stack.last
42
+ when Hash
43
+ @stack.last[@keys.pop] = node
44
+ when Array
45
+ @stack.last << node
46
+ end
47
+ end
48
+ alias :end_array :end_object
49
+
50
+ def start_array
51
+ @stack.push([])
52
+ end
53
+
54
+ def key(key)
55
+ @keys << key
56
+ end
57
+
58
+ def value(value)
59
+ case @stack.last
60
+ when Hash
61
+ @stack.last[@keys.pop] = value
62
+ when Array
63
+ @stack.last << value
64
+ else
65
+ @stack << value
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,316 @@
1
+ module Yajl
2
+ module FFI
3
+ # Raised on any invalid JSON text.
4
+ ParserError = Class.new(RuntimeError)
5
+
6
+ # A streaming JSON parser that generates SAX-like events for state changes.
7
+ #
8
+ # Examples
9
+ #
10
+ # parser = Yajl::FFI::Parser.new
11
+ # parser.key {|key| puts key }
12
+ # parser.value {|value| puts value }
13
+ # parser << '{"answer":'
14
+ # parser << ' 42}'
15
+ class Parser
16
+ BUF_SIZE = 4096
17
+ CONTINUE_PARSE = 1
18
+ FLOAT = /[\.eE]/
19
+
20
+ # Parses a full JSON document from a String or an IO stream and returns
21
+ # the parsed object graph. For parsing small JSON documents with small
22
+ # memory requirements, use the json gem's faster JSON.parse method instead.
23
+ #
24
+ # json - The String or IO containing JSON data.
25
+ #
26
+ # Examples
27
+ #
28
+ # Yajl::FFI::Parser.parse('{"hello": "world"}')
29
+ # # => {"hello": "world"}
30
+ #
31
+ # Raises a Yajl::FFI::ParserError if the JSON data is malformed.
32
+ #
33
+ # Returns a Hash.
34
+ def self.parse(json)
35
+ stream = json.is_a?(String) ? StringIO.new(json) : json
36
+ parser = Parser.new
37
+ builder = Builder.new(parser)
38
+ while (buffer = stream.read(BUF_SIZE)) != nil
39
+ parser << buffer
40
+ end
41
+ parser.finish
42
+ builder.result
43
+ ensure
44
+ stream.close
45
+ end
46
+
47
+ # Create a new parser with an optional initialization block where
48
+ # we can register event callbacks.
49
+ #
50
+ # Examples
51
+ #
52
+ # parser = Yajl::FFI::Parser.new do
53
+ # start_document { puts "start document" }
54
+ # end_document { puts "end document" }
55
+ # start_object { puts "start object" }
56
+ # end_object { puts "end object" }
57
+ # start_array { puts "start array" }
58
+ # end_array { puts "end array" }
59
+ # key {|k| puts "key: #{k}" }
60
+ # value {|v| puts "value: #{v}" }
61
+ # end
62
+ def initialize(&block)
63
+ @listeners = {
64
+ start_document: [],
65
+ end_document: [],
66
+ start_object: [],
67
+ end_object: [],
68
+ start_array: [],
69
+ end_array: [],
70
+ key: [],
71
+ value: []
72
+ }
73
+
74
+ # Track parse stack.
75
+ @depth = 0
76
+ @started = false
77
+
78
+ # Allocate native memory.
79
+ @callbacks = callbacks
80
+ @handle = Yajl::FFI.alloc(@callbacks.to_ptr, nil, nil)
81
+ @handle = ::FFI::AutoPointer.new(@handle, method(:release))
82
+
83
+ # Register any observers in the block.
84
+ instance_eval(&block) if block_given?
85
+ end
86
+
87
+ def start_document(&block)
88
+ @listeners[:start_document] << block
89
+ end
90
+
91
+ def end_document(&block)
92
+ @listeners[:end_document] << block
93
+ end
94
+
95
+ def start_object(&block)
96
+ @listeners[:start_object] << block
97
+ end
98
+
99
+ def end_object(&block)
100
+ @listeners[:end_object] << block
101
+ end
102
+
103
+ def start_array(&block)
104
+ @listeners[:start_array] << block
105
+ end
106
+
107
+ def end_array(&block)
108
+ @listeners[:end_array] << block
109
+ end
110
+
111
+ def key(&block)
112
+ @listeners[:key] << block
113
+ end
114
+
115
+ def value(&block)
116
+ @listeners[:value] << block
117
+ end
118
+
119
+ # Pass data into the parser to advance the state machine and
120
+ # generate callback events. This is well suited for an EventMachine
121
+ # receive_data loop.
122
+ #
123
+ # data - The String of partial JSON data to parse.
124
+ #
125
+ # Raises a Yajl::FFI::ParserError if the JSON data is malformed.
126
+ #
127
+ # Returns nothing.
128
+ def <<(data)
129
+ result = Yajl::FFI.parse(@handle, data, data.bytesize)
130
+ error(data) if result == :error
131
+ if @started && @depth == 0
132
+ result = Yajl::FFI.complete_parse(@handle)
133
+ error(data) if result == :error
134
+ end
135
+ end
136
+
137
+ # Drain any remaining buffered characters into the parser to complete
138
+ # the parsing of the document.
139
+ #
140
+ # This is only required when parsing a document containing a single
141
+ # numeric value, integer or float. The parser has no other way to
142
+ # detect when it should no longer expect additional characters with
143
+ # which to complete the parse, so it must be signaled by a call to
144
+ # this method.
145
+ #
146
+ # If you're parsing more typical object or array documents, there's no
147
+ # need to call `finish` because the parse will complete when the final
148
+ # closing `]` or `}` character is scanned.
149
+ #
150
+ # Raises a Yajl::FFI::ParserError if the JSON data is malformed.
151
+ #
152
+ # Returns nothing.
153
+ def finish
154
+ result = Yajl::FFI.complete_parse(@handle)
155
+ error('') if result == :error
156
+ end
157
+
158
+ private
159
+
160
+ # Raise a ParserError for the malformed JSON data sent to the parser.
161
+ #
162
+ # data - The malformed JSON String that the yajl parser rejected.
163
+ #
164
+ # Returns nothing.
165
+ def error(data)
166
+ pointer = Yajl::FFI.get_error(@handle, 1, data, data.bytesize)
167
+ message = pointer.read_string
168
+ Yajl::FFI.free_error(@handle, pointer)
169
+ raise ParserError, message
170
+ end
171
+
172
+ # Invoke all registered observer procs for the event type.
173
+ #
174
+ # type - The Symbol listener name.
175
+ # args - The argument list to pass into the observer procs.
176
+ #
177
+ # Examples
178
+ #
179
+ # # broadcast events for {"answer": 42}
180
+ # notify(:start_object)
181
+ # notify(:key, "answer")
182
+ # notify(:value, 42)
183
+ # notify(:end_object)
184
+ #
185
+ # Returns nothing.
186
+ def notify(type, *args)
187
+ @started = true
188
+ @listeners[type].each do |block|
189
+ block.call(*args)
190
+ end
191
+ end
192
+
193
+ # Build a native Callbacks struct that broadcasts parser state change
194
+ # events to registered observers.
195
+ #
196
+ # The functions registered in the struct are invoked by the native yajl
197
+ # parser. They convert the yajl callback data into the expected Ruby
198
+ # objects and invoke observers registered on the parser with
199
+ # `start_object`, `key`, `value`, and so on.
200
+ #
201
+ # The struct instance returned from this method must be stored in an
202
+ # instance variable. This prevents the FFI::Function objects from being
203
+ # garbage collected while the parser is still in use. The native function
204
+ # bindings need to be collected at the same time as the Parser instance.
205
+ #
206
+ # Returns a Yajl::FFI::Callbacks struct.
207
+ def callbacks
208
+ callbacks = Yajl::FFI::Callbacks.new
209
+
210
+ callbacks[:on_null] = ::FFI::Function.new(:int, [:pointer]) do |ctx|
211
+ notify(:start_document) if @depth == 0
212
+ notify(:value, nil)
213
+ notify(:end_document) if @depth == 0
214
+ CONTINUE_PARSE
215
+ end
216
+
217
+ callbacks[:on_boolean] = ::FFI::Function.new(:int, [:pointer, :int]) do |ctx, value|
218
+ notify(:start_document) if @depth == 0
219
+ notify(:value, value == 1)
220
+ notify(:end_document) if @depth == 0
221
+ CONTINUE_PARSE
222
+ end
223
+
224
+ # yajl only calls on_number
225
+ callbacks[:on_integer] = nil
226
+ callbacks[:on_double] = nil
227
+
228
+ callbacks[:on_number] = ::FFI::Function.new(:int, [:pointer, :string, :size_t]) do |ctx, value, length|
229
+ notify(:start_document) if @depth == 0
230
+ value = value.slice(0, length)
231
+ number = (value =~ FLOAT) ? value.to_f : value.to_i
232
+ notify(:value, number)
233
+ notify(:end_document) if @depth == 0
234
+ CONTINUE_PARSE
235
+ end
236
+
237
+ callbacks[:on_string] = ::FFI::Function.new(:int, [:pointer, :pointer, :size_t]) do |ctx, value, length|
238
+ notify(:start_document) if @depth == 0
239
+ notify(:value, extract(value, length))
240
+ notify(:end_document) if @depth == 0
241
+ CONTINUE_PARSE
242
+ end
243
+
244
+ callbacks[:on_start_object] = ::FFI::Function.new(:int, [:pointer]) do |ctx|
245
+ @depth += 1
246
+ notify(:start_document) if @depth == 1
247
+ notify(:start_object)
248
+ CONTINUE_PARSE
249
+ end
250
+
251
+ callbacks[:on_key] = ::FFI::Function.new(:int, [:pointer, :pointer, :size_t]) do |ctx, key, length|
252
+ notify(:key, extract(key, length))
253
+ CONTINUE_PARSE
254
+ end
255
+
256
+ callbacks[:on_end_object] = ::FFI::Function.new(:int, [:pointer]) do |ctx|
257
+ @depth -= 1
258
+ notify(:end_object)
259
+ notify(:end_document) if @depth == 0
260
+ CONTINUE_PARSE
261
+ end
262
+
263
+ callbacks[:on_start_array] = ::FFI::Function.new(:int, [:pointer]) do |ctx|
264
+ @depth += 1
265
+ notify(:start_document) if @depth == 1
266
+ notify(:start_array)
267
+ CONTINUE_PARSE
268
+ end
269
+
270
+ callbacks[:on_end_array] = ::FFI::Function.new(:int, [:pointer]) do |ctx|
271
+ @depth -= 1
272
+ notify(:end_array)
273
+ notify(:end_document) if @depth == 0
274
+ CONTINUE_PARSE
275
+ end
276
+
277
+ callbacks
278
+ end
279
+
280
+ # Convert the binary encoded string data passed out of the yajl parser
281
+ # into a UTF-8 encoded string.
282
+ #
283
+ # pointer - The FFI::Pointer containing the ASCII-8BIT encoded String.
284
+ # length - The Fixnum number of characters to extract from `pointer`.
285
+ #
286
+ # Raises a ParserError if the data contains malformed UTF-8 bytes.
287
+ #
288
+ # Returns a String.
289
+ def extract(pointer, length)
290
+ string = pointer.read_string(length)
291
+ string.force_encoding(Encoding::UTF_8)
292
+ unless string.valid_encoding?
293
+ raise ParserError, 'Invalid UTF-8 byte sequence'
294
+ end
295
+ string
296
+ end
297
+
298
+ # Free the memory held by a yajl parser handle previously allocated
299
+ # with Yajl::FFI.alloc.
300
+ #
301
+ # It's not sufficient to just allow the handle pointer to be freed
302
+ # normally because it contains pointers that must also be freed. The
303
+ # native yajl API provides a `yajl_free` function for this purpose.
304
+ #
305
+ # This method is invoked by the FFI::AutoPointer, wrapping the yajl
306
+ # parser handle, when it's garbage collected by Ruby.
307
+ #
308
+ # pointer - The FFI::Pointer that references the native yajl parser.
309
+ #
310
+ # Returns nothing.
311
+ def release(pointer)
312
+ Yajl::FFI.free(pointer)
313
+ end
314
+ end
315
+ end
316
+ end