summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorLamont Granquist <lamont@scriptkiddie.org>2013-12-08 22:47:34 -0800
committerLamont Granquist <lamont@scriptkiddie.org>2013-12-08 22:47:34 -0800
commitc84b1cca5c2bd19d33d1d0d82cbd7721127a0586 (patch)
treeda782d6ddd18d30fb5ae74f90b8293a7c03f34b0 /lib
parent58da300fcf449b713905b886461fab4154df41b1 (diff)
downloadffi-yajl-c84b1cca5c2bd19d33d1d0d82cbd7721127a0586.tar.gz
breaking out ffi encoding into its own class
Diffstat (limited to 'lib')
-rw-r--r--lib/ffi_yajl.rb170
-rw-r--r--lib/ffi_yajl/ext.rb2
-rw-r--r--lib/ffi_yajl/ffi.rb1
-rw-r--r--lib/ffi_yajl/ffi/parser.rb130
4 files changed, 178 insertions, 125 deletions
diff --git a/lib/ffi_yajl.rb b/lib/ffi_yajl.rb
index 65825e9..b3a3901 100644
--- a/lib/ffi_yajl.rb
+++ b/lib/ffi_yajl.rb
@@ -2,6 +2,9 @@ require 'rubygems'
require 'ffi'
module FFI_Yajl
+ class ParseError < StandardError; end
+ class EncodeError < StandardError; end
+
class Encoder
attr_accessor :opts
@@ -70,6 +73,46 @@ module FFI_Yajl
end
end
end
+
+ class Parser
+ attr_accessor :opts
+
+ def self.parse(obj, *args)
+ new(*args).parse(obj)
+ end
+
+ def initialize(opts = {})
+ @opts = opts
+ end
+
+ def parse(str)
+ # initialization that we can do in pure ruby
+ yajl_opts = {}
+
+ # call either the ext or ffi hook
+ do_yajl_parse(str, yajl_opts)
+ end
+
+ if ENV['FORCE_FFI_YAJL'] == "ext"
+ require 'ffi_yajl/ext'
+ include FFI_Yajl::Ext::Parser
+ elsif ENV['FORCE_FFI_YAJL'] == "ffi"
+ require 'ffi_yajl/ffi'
+ include FFI_Yajl::FFI::Parser
+ elsif defined?(Yajl)
+ # on Linux yajl-ruby and non-FFI ffi_yajl conflict
+ require 'ffi_yajl/ffi'
+ include FFI_Yajl::FFI::Parser
+ else
+ begin
+ require 'ffi_yajl/ext'
+ include FFI_Yajl::Ext::Parser
+ rescue LoadError
+ require 'ffi_yajl/ffi'
+ include FFI_Yajl::FFI::Parser
+ end
+ end
+ end
end
module FFI_Yajl
@@ -106,6 +149,7 @@ module FFI_Yajl
:yajl_status_error,
]
+# FFI::Enums are slow, should remove the rest
# enum :yajl_gen_status, [
# :yajl_gen_status_ok,
# :yajl_gen_keys_must_be_strings,
@@ -165,11 +209,10 @@ module FFI_Yajl
# void yajl_gen_free (yajl_gen handle)
attach_function :yajl_gen_free, [:yajl_gen], :void
- # FFI::Enums are slower than :ints
attach_function :yajl_gen_integer, [:yajl_gen, :long_long], :int
attach_function :yajl_gen_double, [:yajl_gen, :double], :int
attach_function :yajl_gen_number, [:yajl_gen, :ustring, :int], :int
- attach_function :yajl_gen_string, [:yajl_gen, :ustring, :int], :int # XXX: FFI Enums are slow?
+ attach_function :yajl_gen_string, [:yajl_gen, :ustring, :int], :int
attach_function :yajl_gen_null, [:yajl_gen], :int
attach_function :yajl_gen_bool, [:yajl_gen, :int], :int
attach_function :yajl_gen_map_open, [:yajl_gen], :int
@@ -181,129 +224,6 @@ module FFI_Yajl
# void yajl_gen_clear (yajl_gen hand)
attach_function :yajl_gen_clear, [:yajl_gen], :void
- class ParseError < StandardError; end
- class EncodeError < StandardError; end
-
- class Parser
- class State
- attr_accessor :stack, :key_stack, :key
-
- def initialize
- @stack = Array.new
- @key_stack = Array.new
- end
-
- def save_key
- key_stack.push(key)
- end
-
- def restore_key
- @key = key_stack.pop()
- end
-
- def set_value(val)
- case stack.last
- when Hash
- raise if key.nil?
- stack.last[key] = val
- when Array
- stack.last.push(val)
- else
- raise
- end
- end
- end
- NullCallback = ::FFI::Function.new(:int, [:pointer]) do |ctx|
- @@CTX_MAPPING[ctx.get_ulong(0)].set_value(nil)
- 1
- end
- BooleanCallback = ::FFI::Function.new(:int, [:pointer, :int]) do |ctx, boolval|
- @@CTX_MAPPING[ctx.get_ulong(0)].set_value(boolval == 1 ? true : false)
- 1
- end
- IntegerCallback = ::FFI::Function.new(:int, [:pointer, :long_long]) do |ctx, intval|
- @@CTX_MAPPING[ctx.get_ulong(0)].set_value(intval)
- 1
- end
- DoubleCallback = ::FFI::Function.new(:int, [:pointer, :double]) do |ctx, doubleval|
- @@CTX_MAPPING[ctx.get_ulong(0)].set_value(doubleval)
- 1
- end
- NumberCallback = ::FFI::Function.new(:int, [:pointer, :pointer, :size_t]) do |ctx, numberval, numberlen|
- raise "NumberCallback: not implemented"
- 1
- end
- StringCallback = ::FFI::Function.new(:int, [:pointer, :string, :size_t]) do |ctx, stringval, stringlen|
- s = stringval.slice(0,stringlen)
- s.force_encoding('UTF-8') if defined? Encoding
- @@CTX_MAPPING[ctx.get_ulong(0)].set_value(s)
- 1
- end
- StartMapCallback = ::FFI::Function.new(:int, [:pointer]) do |ctx|
- state = @@CTX_MAPPING[ctx.get_ulong(0)]
- state.save_key
- state.stack.push(Hash.new)
- 1
- end
- MapKeyCallback = ::FFI::Function.new(:int, [:pointer, :string, :size_t]) do |ctx, key, keylen|
- @@CTX_MAPPING[ctx.get_ulong(0)].key = key.slice(0,keylen)
- 1
- end
- EndMapCallback = ::FFI::Function.new(:int, [:pointer]) do |ctx|
- state = @@CTX_MAPPING[ctx.get_ulong(0)]
- state.restore_key
- state.set_value( state.stack.pop ) if state.stack.length > 1
- 1
- end
- StartArrayCallback = ::FFI::Function.new(:int, [:pointer]) do |ctx|
- state = @@CTX_MAPPING[ctx.get_ulong(0)]
- state.save_key
- state.stack.push(Array.new)
- 1
- end
- EndArrayCallback = ::FFI::Function.new(:int, [:pointer]) do |ctx|
- state = @@CTX_MAPPING[ctx.get_ulong(0)]
- state.restore_key
- @@CTX_MAPPING[ctx.get_ulong(0)].set_value( @@CTX_MAPPING[ctx.get_ulong(0)].stack.pop ) if @@CTX_MAPPING[ctx.get_ulong(0)].stack.length > 1
- 1
- end
-
- def self.parse(str, opts = {})
- @@CTX_MAPPING ||= Hash.new
- rb_ctx = FFI_Yajl::Parser::State.new()
- @@CTX_MAPPING[rb_ctx.object_id] = rb_ctx
- ctx = ::FFI::MemoryPointer.new(:long)
- ctx.write_long( rb_ctx.object_id )
- callback_ptr = ::FFI::MemoryPointer.new(FFI_Yajl::YajlCallbacks)
- callbacks = FFI_Yajl::YajlCallbacks.new(callback_ptr)
- callbacks[:yajl_null] = NullCallback
- callbacks[:yajl_boolean] = BooleanCallback
- callbacks[:yajl_integer] = IntegerCallback
- callbacks[:yajl_double] = DoubleCallback
- callbacks[:yajl_number] = nil #NumberCallback
- callbacks[:yajl_string] = StringCallback
- callbacks[:yajl_start_map] = StartMapCallback
- callbacks[:yajl_map_key] = MapKeyCallback
- callbacks[:yajl_end_map] = EndMapCallback
- callbacks[:yajl_start_array] = StartArrayCallback
- callbacks[:yajl_end_array] = EndArrayCallback
- yajl_handle = FFI_Yajl.yajl_alloc(callback_ptr, nil, ctx)
- if ( stat = FFI_Yajl.yajl_parse(yajl_handle, str, str.bytesize) != :yajl_status_ok )
- # FIXME: dup the error and call yajl_free_error?
- error = FFI_Yajl.yajl_get_error(yajl_handle, 1, str, str.length)
- raise FFI_Yajl::ParseError.new(error)
- end
- if ( stat = FFI_Yajl.yajl_complete_parse(yajl_handle) != :yajl_status_ok )
- # FIXME: dup the error and call yajl_free_error?
- error = FFI_Yajl.yajl_get_error(yajl_handle, 1, str, str.length)
- raise FFI_Yajl::ParseError.new(error)
- end
- rb_ctx.stack.pop
- ensure
- FFI_Yajl.yajl_free(yajl_handle) if yajl_handle
- @@CTX_MAPPING.delete(rb_ctx.object_id) if rb_ctx && rb_ctx.object_id
- end
- end
end
diff --git a/lib/ffi_yajl/ext.rb b/lib/ffi_yajl/ext.rb
index ee78b46..94571df 100644
--- a/lib/ffi_yajl/ext.rb
+++ b/lib/ffi_yajl/ext.rb
@@ -1 +1,3 @@
require 'ffi_yajl/ext/encoder'
+require 'ffi_yajl/ext/parser'
+
diff --git a/lib/ffi_yajl/ffi.rb b/lib/ffi_yajl/ffi.rb
index 0581d4a..6e35a63 100644
--- a/lib/ffi_yajl/ffi.rb
+++ b/lib/ffi_yajl/ffi.rb
@@ -1 +1,2 @@
require 'ffi_yajl/ffi/encoder'
+require 'ffi_yajl/ffi/parser'
diff --git a/lib/ffi_yajl/ffi/parser.rb b/lib/ffi_yajl/ffi/parser.rb
new file mode 100644
index 0000000..acfdafa
--- /dev/null
+++ b/lib/ffi_yajl/ffi/parser.rb
@@ -0,0 +1,130 @@
+module FFI_Yajl
+ module FFI
+ module Parser
+ class State
+ attr_accessor :stack, :key_stack, :key
+
+ def initialize
+ @stack = Array.new
+ @key_stack = Array.new
+ end
+
+ def save_key
+ key_stack.push(key)
+ end
+
+ def restore_key
+ @key = key_stack.pop()
+ end
+
+ def set_value(val)
+ case stack.last
+ when Hash
+ raise if key.nil?
+ stack.last[key] = val
+ when Array
+ stack.last.push(val)
+ else
+ raise
+ end
+ end
+ end
+
+ def setup_callbacks
+ ctx_mapping = @ctx_mapping # get a local alias
+ @null_callback = ::FFI::Function.new(:int, [:pointer]) do |ctx|
+ ctx_mapping[ctx.get_ulong(0)].set_value(nil)
+ 1
+ end
+ @boolean_callback = ::FFI::Function.new(:int, [:pointer, :int]) do |ctx, boolval|
+ ctx_mapping[ctx.get_ulong(0)].set_value(boolval == 1 ? true : false)
+ 1
+ end
+ @integer_callback = ::FFI::Function.new(:int, [:pointer, :long_long]) do |ctx, intval|
+ ctx_mapping[ctx.get_ulong(0)].set_value(intval)
+ 1
+ end
+ @double_callback = ::FFI::Function.new(:int, [:pointer, :double]) do |ctx, doubleval|
+ ctx_mapping[ctx.get_ulong(0)].set_value(doubleval)
+ 1
+ end
+ @number_callback = ::FFI::Function.new(:int, [:pointer, :pointer, :size_t]) do |ctx, numberval, numberlen|
+ raise "NumberCallback: not implemented"
+ 1
+ end
+ @string_callback = ::FFI::Function.new(:int, [:pointer, :string, :size_t]) do |ctx, stringval, stringlen|
+ s = stringval.slice(0,stringlen)
+ s.force_encoding('UTF-8') if defined? Encoding
+ ctx_mapping[ctx.get_ulong(0)].set_value(s)
+ 1
+ end
+ @start_map_callback = ::FFI::Function.new(:int, [:pointer]) do |ctx|
+ state = ctx_mapping[ctx.get_ulong(0)]
+ state.save_key
+ state.stack.push(Hash.new)
+ 1
+ end
+ @map_key_callback = ::FFI::Function.new(:int, [:pointer, :string, :size_t]) do |ctx, key, keylen|
+ ctx_mapping[ctx.get_ulong(0)].key = key.slice(0,keylen)
+ 1
+ end
+ @end_map_callback = ::FFI::Function.new(:int, [:pointer]) do |ctx|
+ state = ctx_mapping[ctx.get_ulong(0)]
+ state.restore_key
+ state.set_value( state.stack.pop ) if state.stack.length > 1
+ 1
+ end
+ @start_array_callback = ::FFI::Function.new(:int, [:pointer]) do |ctx|
+ state = ctx_mapping[ctx.get_ulong(0)]
+ state.save_key
+ state.stack.push(Array.new)
+ 1
+ end
+ @end_array_callback = ::FFI::Function.new(:int, [:pointer]) do |ctx|
+ state = ctx_mapping[ctx.get_ulong(0)]
+ state.restore_key
+ ctx_mapping[ctx.get_ulong(0)].set_value( ctx_mapping[ctx.get_ulong(0)].stack.pop ) if ctx_mapping[ctx.get_ulong(0)].stack.length > 1
+ 1
+ end
+ end
+
+ def do_yajl_parse(str, opts = {})
+ @ctx_mapping ||= Hash.new
+ setup_callbacks
+ rb_ctx = ::FFI_Yajl::FFI::Parser::State.new()
+ @ctx_mapping[rb_ctx.object_id] = rb_ctx
+ ctx = ::FFI::MemoryPointer.new(:long)
+ ctx.write_long( rb_ctx.object_id )
+ callback_ptr = ::FFI::MemoryPointer.new(::FFI_Yajl::YajlCallbacks)
+ callbacks = ::FFI_Yajl::YajlCallbacks.new(callback_ptr)
+ callbacks[:yajl_null] = @null_callback
+ callbacks[:yajl_boolean] = @boolean_callback
+ callbacks[:yajl_integer] = @integer_callback
+ callbacks[:yajl_double] = @double_callback
+ callbacks[:yajl_number] = nil #NumberCallback
+ callbacks[:yajl_string] = @string_callback
+ callbacks[:yajl_start_map] = @start_map_callback
+ callbacks[:yajl_map_key] = @map_key_callback
+ callbacks[:yajl_end_map] = @end_map_callback
+ callbacks[:yajl_start_array] = @start_array_callback
+ callbacks[:yajl_end_array] = @end_array_callback
+ yajl_handle = ::FFI_Yajl.yajl_alloc(callback_ptr, nil, ctx)
+ if ( stat = ::FFI_Yajl.yajl_parse(yajl_handle, str, str.bytesize) != :yajl_status_ok )
+ # FIXME: dup the error and call yajl_free_error?
+ error = ::FFI_Yajl.yajl_get_error(yajl_handle, 1, str, str.length)
+ raise ::FFI_Yajl::ParseError.new(error)
+ end
+ if ( stat = FFI_Yajl.yajl_complete_parse(yajl_handle) != :yajl_status_ok )
+ # FIXME: dup the error and call yajl_free_error?
+ error = ::FFI_Yajl.yajl_get_error(yajl_handle, 1, str, str.length)
+ raise ::FFI_Yajl::ParseError.new(error)
+ end
+ rb_ctx.stack.pop
+ ensure
+ ::FFI_Yajl.yajl_free(yajl_handle) if yajl_handle
+ @ctx_mapping.delete(rb_ctx.object_id) if rb_ctx && rb_ctx.object_id
+ end
+ end
+ end
+end
+