diff options
author | skeshari12 <skeshari@msystechnologies.com> | 2022-04-12 15:50:38 +0530 |
---|---|---|
committer | skeshari12 <skeshari@msystechnologies.com> | 2022-04-12 15:50:38 +0530 |
commit | 0f05b88d19bf180499d4401c41918ff4a87b4d67 (patch) | |
tree | 9324430034066a5d77b7baae60de2f19c289735b | |
parent | 66ca90cec495a545638d65ca307dee35b382d322 (diff) | |
download | ffi-yajl-0f05b88d19bf180499d4401c41918ff4a87b4d67.tar.gz |
fix error
Signed-off-by: skeshari12 <skeshari@msystechnologies.com>
-rw-r--r-- | Gemfile | 14 | ||||
-rw-r--r-- | ext/dlopen/dlopen.c | 40 | ||||
-rw-r--r-- | ext/dlopen/extconf.rb | 16 | ||||
-rw-r--r-- | ext/encoder/encoder.c | 394 | ||||
-rw-r--r-- | ext/encoder/extconf.rb | 65 | ||||
-rw-r--r-- | ext/parser/extconf.rb | 65 | ||||
-rw-r--r-- | ext/parser/parser.c | 240 |
7 files changed, 830 insertions, 4 deletions
@@ -6,13 +6,19 @@ group :development do # for testing loading concurrently with yajl-ruby, not on jruby # gem 'yajl-ruby', platforms: [ :ruby, :mswin, :mingw ] gem "ffi" - gem "rake", ">= 10.1" - gem "rspec", "~> 3.0" + gem "rake"#, ">= 10.1" + gem "rspec"#, "~> 3.0" gem "pry", "~> 0.9" - gem "rake-compiler", "~> 1.0" - gem "rack", "~> 2.0" + gem "rake-compiler"#, "~> 1.0" + gem "rack"#, "~> 2.0" end group :development_extras do gem "chefstyle" end + +instance_eval(ENV["GEMFILE_MOD"]) if ENV["GEMFILE_MOD"] + +# If you want to load debugging tools into the bundle exec sandbox, +# add these additional dependencies into Gemfile.local +eval_gemfile(__FILE__ + ".local") if File.exist?(__FILE__ + ".local")
\ No newline at end of file diff --git a/ext/dlopen/dlopen.c b/ext/dlopen/dlopen.c new file mode 100644 index 0000000..eb81a21 --- /dev/null +++ b/ext/dlopen/dlopen.c @@ -0,0 +1,40 @@ +#include <ruby.h> + +#if defined(HAVE_DLFCN_H) +# include <dlfcn.h> +#ifndef RTLD_LAZY +#define RTLD_LAZY 0 +#endif +#ifndef RTLD_GLOBAL +#define RTLD_GLOBAL 0 +#endif +#ifndef RTLD_NOW +#define RTLD_NOW 0 +#endif +#else +# if defined(_WIN32) +# include <windows.h> +# define dlopen(name,flag) ((void*)LoadLibrary(name)) +# define dlerror() strerror(rb_w32_map_errno(GetLastError())) +# define dlsym(handle,name) ((void*)GetProcAddress((handle),(name))) +# define RTLD_LAZY -1 +# define RTLD_NOW -1 +# define RTLD_GLOBAL -1 +# endif +#endif + +static VALUE mFFI_Yajl, mDlopen, mExt; + +static VALUE mDlopen_dlopen(VALUE self, VALUE file) { + if (dlopen(RSTRING_PTR(file), RTLD_NOW|RTLD_GLOBAL) == NULL) { + rb_raise(rb_eArgError, "%s", dlerror()); + } + return Qnil; +} + +void Init_dlopen() { + mFFI_Yajl = rb_define_module("FFI_Yajl"); + mExt = rb_define_module_under(mFFI_Yajl, "Ext"); + mDlopen = rb_define_module_under(mExt, "Dlopen"); + rb_define_method(mDlopen, "dlopen", mDlopen_dlopen, 1); +} diff --git a/ext/dlopen/extconf.rb b/ext/dlopen/extconf.rb new file mode 100644 index 0000000..803d326 --- /dev/null +++ b/ext/dlopen/extconf.rb @@ -0,0 +1,16 @@ +# rubocop:disable Style/GlobalVars +require "mkmf" +require "rubygems" + +RbConfig::MAKEFILE_CONFIG["CC"] = ENV["CC"] if ENV["CC"] + +puts $CFLAGS +puts $LDFLAGS + +have_header("dlfcn.h") + +have_library("dl", "dlopen") + +dir_config "dlopen" + +create_makefile "ffi_yajl/ext/dlopen" diff --git a/ext/encoder/encoder.c b/ext/encoder/encoder.c new file mode 100644 index 0000000..6f1492c --- /dev/null +++ b/ext/encoder/encoder.c @@ -0,0 +1,394 @@ +#include <ruby.h> +#include <yajl/yajl_gen.h> + +static VALUE mFFI_Yajl, mExt, mEncoder, mEncoder2, cEncodeError; +static VALUE cDate, cTime, cDateTime, cStringIO; +static VALUE cYajl_Gen; + +/* FIXME: the json gem does a whole bunch of indirection around monkeypatching... not sure if we need to as well... */ + +static VALUE mEncoder_do_yajl_encode(VALUE self, VALUE obj, VALUE yajl_gen_opts, VALUE json_opts) { + ID sym_ffi_yajl = rb_intern("ffi_yajl"); + VALUE sym_yajl_gen_beautify = ID2SYM(rb_intern("yajl_gen_beautify")); + VALUE sym_yajl_gen_validate_utf8 = ID2SYM(rb_intern("yajl_gen_validate_utf8")); + VALUE sym_yajl_gen_indent_string = ID2SYM(rb_intern("yajl_gen_indent_string")); + yajl_gen yajl_gen; + const unsigned char *buf; + size_t len; + VALUE state; + VALUE ret; + VALUE indent_string; + VALUE rb_yajl_gen; + + yajl_gen = yajl_gen_alloc(NULL); + + if ( rb_hash_aref(yajl_gen_opts, sym_yajl_gen_beautify) == Qtrue ) { + yajl_gen_config(yajl_gen, yajl_gen_beautify, 1); + } + if ( rb_hash_aref(yajl_gen_opts, sym_yajl_gen_validate_utf8) == Qtrue ) { + yajl_gen_config(yajl_gen, yajl_gen_validate_utf8, 1); + } + + indent_string = rb_hash_aref(yajl_gen_opts, sym_yajl_gen_indent_string); + if (indent_string != Qnil) { + yajl_gen_config(yajl_gen, yajl_gen_indent_string, RSTRING_PTR(indent_string)); + } else { + yajl_gen_config(yajl_gen, yajl_gen_indent_string, " "); + } + + state = rb_hash_new(); + + rb_hash_aset(state, rb_str_new2("processing_key"), Qfalse); + + rb_hash_aset(state, rb_str_new2("json_opts"), json_opts); + + rb_yajl_gen = Data_Wrap_Struct(cYajl_Gen, NULL, NULL, yajl_gen); + + rb_funcall(obj, sym_ffi_yajl, 2, rb_yajl_gen, state); + + yajl_gen_get_buf(yajl_gen, &buf, &len); + + ret = rb_str_new2((char *)buf); + + yajl_gen_free(yajl_gen); + + return ret; +} + +int rb_cHash_ffi_yajl_callback(VALUE key, VALUE val, VALUE extra) { + ID sym_ffi_yajl = rb_intern("ffi_yajl"); + VALUE state = rb_hash_aref(extra, rb_str_new2("state")); + VALUE rb_yajl_gen = rb_hash_aref(extra, rb_str_new2("yajl_gen")); + + rb_hash_aset(state, rb_str_new2("processing_key"), Qtrue); + rb_funcall(key, sym_ffi_yajl, 2, rb_yajl_gen, state); + rb_hash_aset(state, rb_str_new2("processing_key"), Qfalse); + + rb_funcall(val, sym_ffi_yajl, 2, rb_yajl_gen, state); + + return 0; +} + +#define RB_FUNC0(call) rb_funcall(self, rb_intern(call), 0) + +/* + * wrappers around yajl_gen_* functions + */ + +/* encode a c-string as a yajl string */ +VALUE gen_cstring(VALUE rb_yajl_gen, char *cptr, int len) { + yajl_gen_status status; + struct yajl_gen_t *yajl_gen; + Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen); + + if ((status = yajl_gen_string(yajl_gen, (unsigned char *)cptr, len)) != yajl_gen_status_ok) { + rb_funcall(mEncoder2, rb_intern("raise_error_for_status"), 2, INT2FIX(status), rb_str_new(cptr, len)); + } + + return Qnil; +} + +/* encode a ruby-sring as a yajl string */ +VALUE gen_string(VALUE rb_yajl_gen, VALUE str) { + char *cptr = RSTRING_PTR(str); + int len = RSTRING_LEN(str); + + return gen_cstring(rb_yajl_gen, cptr, len); +} + +/* calls #to_s on an object to encode it as a yajl string */ +static VALUE gen_string_to_s(VALUE rb_yajl_gen, VALUE self) { + return gen_string(rb_yajl_gen, RB_FUNC0("to_s")); +} + +/* encode a ruby string as a yajl number (also used to embed already-rendered json from #to_json) */ +VALUE gen_number(VALUE rb_yajl_gen, VALUE str) { + yajl_gen_status status; + struct yajl_gen_t *yajl_gen; + Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen); + char *cptr = RSTRING_PTR(str); + int len = RSTRING_LEN(str); + + if ((status = yajl_gen_number(yajl_gen, cptr, len)) != yajl_gen_status_ok) { + rb_funcall(mEncoder2, rb_intern("raise_error_for_status"), 2, INT2FIX(status), str); + } + + return Qnil; +} + +/* encode hash open */ +VALUE gen_map_open(VALUE rb_yajl_gen) { + yajl_gen_status status; + struct yajl_gen_t *yajl_gen; + Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen); + + if ((status = yajl_gen_map_open(yajl_gen)) != yajl_gen_status_ok) { + rb_funcall(mEncoder2, rb_intern("raise_error_for_status"), 2, INT2FIX(status), rb_str_new2("{")); + } + + return Qnil; +} + +/* encode a hash close */ +VALUE gen_map_close(VALUE rb_yajl_gen) { + yajl_gen_status status; + struct yajl_gen_t *yajl_gen; + Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen); + + if ((status = yajl_gen_map_close(yajl_gen)) != yajl_gen_status_ok) { + rb_funcall(mEncoder2, rb_intern("raise_error_for_status"), 2, INT2FIX(status), rb_str_new2("}")); + } + + return Qnil; +} + +/* encode an array open */ +VALUE gen_array_open(VALUE rb_yajl_gen) { + yajl_gen_status status; + struct yajl_gen_t *yajl_gen; + Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen); + + if ((status = yajl_gen_array_open(yajl_gen)) != yajl_gen_status_ok) { + rb_funcall(mEncoder2, rb_intern("raise_error_for_status"), 2, INT2FIX(status), rb_str_new2("[")); + } + + return Qnil; +} + +/* encode an array close */ +VALUE gen_array_close(VALUE rb_yajl_gen) { + yajl_gen_status status; + struct yajl_gen_t *yajl_gen; + Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen); + + if ((status = yajl_gen_array_close(yajl_gen)) != yajl_gen_status_ok) { + rb_funcall(mEncoder2, rb_intern("raise_error_for_status"), 2, INT2FIX(status), rb_str_new2("]")); + } + + return Qnil; +} + +/* encode a json null */ +VALUE gen_null(VALUE rb_yajl_gen) { + yajl_gen_status status; + struct yajl_gen_t *yajl_gen; + Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen); + + if ((status = yajl_gen_null(yajl_gen)) != yajl_gen_status_ok) { + rb_funcall(mEncoder2, rb_intern("raise_error_for_status"), 2, INT2FIX(status), rb_str_new2("null")); + } + + return Qnil; +} + +/* encode a true value */ +VALUE gen_true(VALUE rb_yajl_gen) { + yajl_gen_status status; + struct yajl_gen_t *yajl_gen; + Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen); + + if ((status = yajl_gen_bool(yajl_gen, 1)) != yajl_gen_status_ok) { + rb_funcall(mEncoder2, rb_intern("raise_error_for_status"), 2, INT2FIX(status), rb_str_new2("true")); + } + + return Qnil; +} + +/* encode a false value */ +VALUE gen_false(VALUE rb_yajl_gen) { + yajl_gen_status status; + struct yajl_gen_t *yajl_gen; + Data_Get_Struct(rb_yajl_gen, struct yajl_gen_t, yajl_gen); + + if ((status = yajl_gen_bool(yajl_gen, 0)) != yajl_gen_status_ok) { + rb_funcall(mEncoder2, rb_intern("raise_error_for_status"), 2, INT2FIX(status), rb_str_new2("false")); + } + + return Qnil; +} + +/* + * <Object>#to_ffi_yajl() method calls + */ + +static VALUE rb_cHash_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) { + if ( rb_hash_aref(state, rb_str_new2("processing_key")) == Qtrue ) { + gen_string(rb_yajl_gen, rb_funcall(self, rb_intern("to_s"), 0)); + } else { + + /* FIXME: i think this got refactored from something else and it is now pointless -- + should just pass rb_yajl_gen directly instead of this 'extra' hash -- i don't + *think* this indirection is doing anything useful to mark memory against the GC */ + + VALUE extra = rb_hash_new(); + + rb_hash_aset(extra, rb_str_new2("yajl_gen"), rb_yajl_gen); + + rb_hash_aset(extra, rb_str_new2("state"), state); + + gen_map_open(rb_yajl_gen); + + rb_hash_foreach(self, rb_cHash_ffi_yajl_callback, extra); + + gen_map_close(rb_yajl_gen); + } + + return Qnil; +} + +static VALUE rb_cArray_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) { + if ( rb_hash_aref(state, rb_str_new2("processing_key")) == Qtrue ) { + gen_string(rb_yajl_gen, rb_funcall(self, rb_intern("to_s"), 0)); + } else { + VALUE val; + long i; + ID sym_ffi_yajl = rb_intern("ffi_yajl"); + + gen_array_open(rb_yajl_gen); + + for(i=0; i<RARRAY_LEN(self); i++) { + val = rb_ary_entry(self, i); + rb_funcall(val, sym_ffi_yajl, 2, rb_yajl_gen, state); + } + + gen_array_close(rb_yajl_gen); + } + + return Qnil; +} + +static VALUE rb_cNilClass_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) { + if ( rb_hash_aref(state, rb_str_new2("processing_key")) == Qtrue ) { + gen_cstring(rb_yajl_gen, "", sizeof("")-1); + } else { + gen_null(rb_yajl_gen); + } + + return Qnil; +} + +static VALUE rb_cTrueClass_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) { + if ( rb_hash_aref(state, rb_str_new2("processing_key")) == Qtrue ) { + gen_cstring(rb_yajl_gen, "true", sizeof("true")-1); + } else { + gen_true(rb_yajl_gen); + } + + return Qnil; +} + +static VALUE rb_cFalseClass_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) { + if ( rb_hash_aref(state, rb_str_new2("processing_key")) == Qtrue ) { + gen_cstring(rb_yajl_gen, "false", sizeof("false")-1); + } else { + gen_false(rb_yajl_gen); + } + + return Qnil; +} + +static VALUE rb_cFixnum_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) { + VALUE str = rb_funcall(self, rb_intern("to_s"), 0); + char *cptr = RSTRING_PTR(str); + + if (memcmp(cptr, "NaN", sizeof("NaN")) == 0 || memcmp(cptr, "Infinity", sizeof("Infinity")) == 0 || memcmp(cptr, "-Infinity", sizeof("-Infinity")) == 0) { + rb_raise(cEncodeError, "'%s' is an invalid number", cptr); + } + + if ( rb_hash_aref(state, rb_str_new2("processing_key")) == Qtrue ) { + gen_string(rb_yajl_gen, str); + } else { + gen_number(rb_yajl_gen, str); + } + + return Qnil; +} + +static VALUE rb_cBignum_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) { + return rb_cFixnum_ffi_yajl(self, rb_yajl_gen, state); +} + +static VALUE rb_cFloat_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) { + return rb_cFixnum_ffi_yajl(self, rb_yajl_gen, state); +} + +static VALUE rb_cString_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) { + return gen_string(rb_yajl_gen, self); +} + +static VALUE rb_cSymbol_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) { + return gen_string_to_s(rb_yajl_gen, self); +} + +static VALUE rb_cDate_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) { + return gen_string_to_s(rb_yajl_gen, self); +} + +static VALUE rb_cTime_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) { + ID sym_strftime = rb_intern("strftime"); + VALUE str = rb_funcall(self, sym_strftime, 1, rb_str_new2("%Y-%m-%d %H:%M:%S %z")); + + return gen_string(rb_yajl_gen, str); +} + +static VALUE rb_cStringIO_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) { + return gen_string(rb_yajl_gen, RB_FUNC0("read")); +} + +static VALUE rb_cDateTime_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) { + return gen_string_to_s(rb_yajl_gen, self); +} + +static VALUE rb_cObject_ffi_yajl(VALUE self, VALUE rb_yajl_gen, VALUE state) { + ID sym_to_json = rb_intern("to_json"); + if ( rb_hash_aref(state, rb_str_new2("processing_key")) != Qtrue && rb_respond_to(self, sym_to_json) ) { + VALUE json_opts = rb_hash_aref(state, rb_str_new2("json_opts")); + VALUE str = rb_funcall(self, sym_to_json, 1, json_opts); + + gen_number(rb_yajl_gen, str); + } else { + gen_string_to_s(rb_yajl_gen, self); + } + + return Qnil; +} + +void Init_encoder() { + mFFI_Yajl = rb_define_module("FFI_Yajl"); + mEncoder2 = rb_define_class_under(mFFI_Yajl, "Encoder", rb_cObject); + cEncodeError = rb_define_class_under(mFFI_Yajl, "EncodeError", rb_eStandardError); + mExt = rb_define_module_under(mFFI_Yajl, "Ext"); + mEncoder = rb_define_module_under(mExt, "Encoder"); + cYajl_Gen = rb_define_class_under(mEncoder, "YajlGen", rb_cObject); + rb_define_method(mEncoder, "do_yajl_encode", mEncoder_do_yajl_encode, 3); + + /* use rb_const_get instead of rb_define_class so that we don't get superclass mismatches */ + ID sym_Date = rb_intern("Date"); + cDate = rb_const_get(rb_cObject, sym_Date); + ID sym_Time = rb_intern("Time"); + cTime = rb_const_get(rb_cObject, sym_Time); + ID sym_DateTime = rb_intern("DateTime"); + cDateTime = rb_const_get(rb_cObject, sym_DateTime); + ID sym_StringIO = rb_intern("StringIO"); + cStringIO = rb_const_get(rb_cObject, sym_StringIO); + + rb_define_method(rb_cHash, "ffi_yajl", rb_cHash_ffi_yajl, 2); + rb_define_method(rb_cArray, "ffi_yajl", rb_cArray_ffi_yajl, 2); + rb_define_method(rb_cNilClass, "ffi_yajl", rb_cNilClass_ffi_yajl, 2); + rb_define_method(rb_cTrueClass, "ffi_yajl", rb_cTrueClass_ffi_yajl, 2); + rb_define_method(rb_cFalseClass, "ffi_yajl", rb_cFalseClass_ffi_yajl, 2); +#ifdef rb_cFixnum /* ruby < 2.4 */ + rb_define_method(rb_cFixnum, "ffi_yajl", rb_cFixnum_ffi_yajl, 2); + rb_define_method(rb_cBignum, "ffi_yajl", rb_cBignum_ffi_yajl, 2); +#else + rb_define_method(rb_cInteger, "ffi_yajl", rb_cFixnum_ffi_yajl, 2); +#endif + rb_define_method(rb_cFloat, "ffi_yajl", rb_cFloat_ffi_yajl, 2); + rb_define_method(rb_cString, "ffi_yajl", rb_cString_ffi_yajl, 2); + rb_define_method(rb_cSymbol, "ffi_yajl", rb_cSymbol_ffi_yajl, 2); + rb_define_method(cDate, "ffi_yajl", rb_cDate_ffi_yajl, 2); + rb_define_method(cTime, "ffi_yajl", rb_cTime_ffi_yajl, 2); + rb_define_method(cDateTime, "ffi_yajl", rb_cDateTime_ffi_yajl, 2); + rb_define_method(cStringIO, "ffi_yajl", rb_cStringIO_ffi_yajl, 2); + rb_define_method(rb_cObject, "ffi_yajl", rb_cObject_ffi_yajl, 2); +} diff --git a/ext/encoder/extconf.rb b/ext/encoder/extconf.rb new file mode 100644 index 0000000..8f7d14b --- /dev/null +++ b/ext/encoder/extconf.rb @@ -0,0 +1,65 @@ +# rubocop:disable Style/GlobalVars +require "mkmf" +require "rubygems" +require "libyajl2" + +RbConfig::MAKEFILE_CONFIG["CC"] = ENV["CC"] if ENV["CC"] + +# pick up the vendored libyajl2 out of the libyajl2 gem +$CFLAGS = " -I#{Libyajl2.include_path} #{$CFLAGS}" +$LDFLAGS = " -L#{Libyajl2.opt_path} #{$LDFLAGS}" + +# remove "-Wl,--no-undefined" flag if existent to allow for loading with dlopen +$LDFLAGS.slice!("-Wl,--no-undefined") + +puts $CFLAGS +puts $LDFLAGS + +# except if you're doing an unoptimized gcc install we're going to help you out a bit +if RbConfig::MAKEFILE_CONFIG["CC"] =~ /gcc|clang/ + $CFLAGS << " -O3" unless $CFLAGS[/-O\d/] + # how many people realize that -Wall is a compiler-specific flag??? + # apparently not many based on reading lots of shitty extconf.rb's out there + $CFLAGS << " -Wall" +end + +def windows? + !!(RUBY_PLATFORM =~ /mswin|mingw|cygwin|windows/) +end + +if windows? + # include our libyajldll.a definitions on windows in the libyajl2 gem + $libs = "#{$libs} -lyajldll" +end + +# NOTE: find_library has the side effect of adding -lyajl to the flags which we are deliberately +# avoiding doing with the libyajl2-gem (allowing it to be lazily loaded with dlopen) +if !windows? && !find_header("yajl/yajl_tree.h") + puts "libyajl2 headers not found in libyajl2-gem, searching for system libraries..." + + HEADER_DIRS = [ + "/opt/local/include", # MacPorts + "/usr/local/include", # /usr/local + RbConfig::CONFIG["includedir"], # Ruby + "/usr/include", # (default) + ].freeze + + LIB_DIRS = [ + "/opt/local/lib", # MacPorts + "/usr/local/lib", # /usr/local + Homebrew + RbConfig::CONFIG["libdir"], # Ruby + "/usr/lib", # (default) + ].freeze + + # add --with-yajl-dir, --with-yajl-include, --with-yajl-lib + dir_config("yajl", HEADER_DIRS, LIB_DIRS) + + # here we use find_library in order to deliberately link with -lyajl as a useful side-effect + unless find_header("yajl/yajl_tree.h") && find_library("yajl", "yajl_complete_parse") + abort "libyajl2 is missing. please install libyajl2" + end +end + +dir_config "encoder" + +create_makefile "ffi_yajl/ext/encoder" diff --git a/ext/parser/extconf.rb b/ext/parser/extconf.rb new file mode 100644 index 0000000..5611934 --- /dev/null +++ b/ext/parser/extconf.rb @@ -0,0 +1,65 @@ +# rubocop:disable Style/GlobalVars +require "mkmf" +require "rubygems" +require "libyajl2" + +RbConfig::MAKEFILE_CONFIG["CC"] = ENV["CC"] if ENV["CC"] + +# pick up the vendored libyajl2 out of the libyajl2 gem +$CFLAGS = "-I#{Libyajl2.include_path} #{$CFLAGS}" +$LDFLAGS = "-L#{Libyajl2.opt_path} #{$LDFLAGS}" + +# remove "-Wl,--no-undefined" flag if existent to allow for loading with dlopen +$LDFLAGS.slice!("-Wl,--no-undefined") + +puts $CFLAGS +puts $LDFLAGS + +# except if you're doing an unoptimized gcc install we're going to help you out a bit +if RbConfig::MAKEFILE_CONFIG["CC"] =~ /gcc|clang/ + $CFLAGS << " -O3" unless $CFLAGS[/-O\d/] + # how many people realize that -Wall is a compiler-specific flag??? + # apparently not many based on reading lots of shitty extconf.rb's out there + $CFLAGS << " -Wall" +end + +def windows? + !!(RUBY_PLATFORM =~ /mswin|mingw|cygwin|windows/) +end + +if windows? + # include our libyajldll.a definitions on windows in the libyajl2 gem + $libs = "#{$libs} -lyajldll" +end + +# NOTE: find_library has the side effect of adding -lyajl to the flags which we are deliberately +# avoiding doing with the libyajl2-gem (allowing it to be lazily loaded with dlopen) +if !windows? && !find_header("yajl/yajl_tree.h") + puts "libyajl2 headers not found in libyajl2-gem, searching for system libraries..." + + HEADER_DIRS = [ + "/opt/local/include", # MacPorts + "/usr/local/include", # /usr/local + RbConfig::CONFIG["includedir"], # Ruby + "/usr/include", # (default) + ].freeze + + LIB_DIRS = [ + "/opt/local/lib", # MacPorts + "/usr/local/lib", # /usr/local + Homebrew + RbConfig::CONFIG["libdir"], # Ruby + "/usr/lib", # (default) + ].freeze + + # add --with-yajl-dir, --with-yajl-include, --with-yajl-lib + dir_config("yajl", HEADER_DIRS, LIB_DIRS) + + # here we use find_library in order to deliberately link with -lyajl as a useful side-effect + unless find_header("yajl/yajl_tree.h") && find_library("yajl", "yajl_complete_parse") + abort "libyajl2 is missing. please install libyajl2" + end +end + +dir_config "parser" + +create_makefile "ffi_yajl/ext/parser" diff --git a/ext/parser/parser.c b/ext/parser/parser.c new file mode 100644 index 0000000..c0b8eab --- /dev/null +++ b/ext/parser/parser.c @@ -0,0 +1,240 @@ +#include <ruby.h> +#include <yajl/yajl_parse.h> + +#ifdef HAVE_RUBY_ENCODING_H +#include <ruby/encoding.h> +static rb_encoding *utf8Encoding; +#endif + +static VALUE mFFI_Yajl, mExt, mParser, cParseError; + +typedef struct { + VALUE self; + int symbolizeKeys; + int uniqueKeyChecking; +} CTX; + +void set_value(CTX *ctx, VALUE val) { + VALUE stack = rb_ivar_get(ctx->self, rb_intern("stack")); + VALUE key = rb_ivar_get(ctx->self, rb_intern("key")); + long len = RARRAY_LEN(stack); + VALUE last = rb_ary_entry(stack, len-1); + switch (TYPE(last)) { + case T_ARRAY: + rb_ary_push(last, val); + break; + case T_HASH: + if ( ctx->uniqueKeyChecking ) { + ID sym_has_key = rb_intern("has_key?"); + if ( rb_funcall(last, sym_has_key, 1, key) == Qtrue ) { + rb_raise(cParseError, "repeated key: %s", RSTRING_PTR(key)); + } + } + rb_hash_aset(last, key, val); + break; + default: + rb_ary_push(stack, val); + break; + } +} + +void set_key(CTX *ctx, VALUE key) { + rb_ivar_set(ctx->self, rb_intern("key"), key); +} + +void start_object(CTX *ctx, VALUE obj) { + VALUE key_stack = rb_ivar_get(ctx->self, rb_intern("key_stack")); + VALUE key = rb_ivar_get(ctx->self, rb_intern("key")); + VALUE stack = rb_ivar_get(ctx->self, rb_intern("stack")); + + rb_ary_push(key_stack, key); + rb_ary_push(stack, obj); +} + +void end_object(CTX *ctx) { + VALUE key_stack = rb_ivar_get(ctx->self, rb_intern("key_stack")); + VALUE stack = rb_ivar_get(ctx->self, rb_intern("stack")); + rb_ivar_set(ctx->self, rb_intern("key"), rb_ary_pop(key_stack)); + if ( RARRAY_LEN(stack) > 1 ) { + set_value(ctx, rb_ary_pop(stack)); + } +} + +int null_callback(void *ctx) { + set_value(ctx, Qnil); + return 1; +} + +int boolean_callback(void *ctx, int boolean) { + set_value(ctx, boolean ? Qtrue : Qfalse); + return 1; +} + +int integer_callback(void *ctx, long long intVal) { + set_value(ctx, LONG2NUM(intVal)); + return 1; +} + +int double_callback(void *ctx, double doubleVal) { + set_value(ctx, rb_float_new(doubleVal)); + return 1; +} + +int number_callback(void *ctx, const char *numberVal, size_t numberLen) { + char *buf = (char *)malloc(numberLen+1); + buf[numberLen] = 0; + memcpy(buf, numberVal, numberLen); + if (memchr(buf, '.', numberLen) || + memchr(buf, 'e', numberLen) || + memchr(buf, 'E', numberLen)) { + set_value(ctx, rb_float_new(strtod(buf, NULL))); + } else { + set_value(ctx, rb_cstr2inum(buf, 10)); + } + free(buf); + return 1; +} + +int string_callback(void *ctx, const unsigned char *stringVal, size_t stringLen) { + VALUE str = rb_str_new((const char *)stringVal, stringLen); +#ifdef HAVE_RUBY_ENCODING_H + rb_encoding *default_internal_enc = rb_default_internal_encoding(); + rb_enc_associate(str, utf8Encoding); + if (default_internal_enc) { + str = rb_str_export_to_enc(str, default_internal_enc); + } +#endif + set_value(ctx,str); + return 1; +} + +int start_map_callback(void *ctx) { + start_object(ctx,rb_hash_new()); + return 1; +} + +int map_key_callback(void *ctx, const unsigned char *stringVal, size_t stringLen) { + VALUE key; +#ifdef HAVE_RUBY_ENCODING_H + rb_encoding *default_internal_enc; +#endif + + if ( ((CTX *)ctx)->symbolizeKeys ) { +#ifdef HAVE_RUBY_ENCODING_H + ID id = rb_intern3((const char *)stringVal, stringLen, utf8Encoding); + key = ID2SYM(id); +#else + VALUE str = rb_str_new((const char *)stringVal, stringLen); + key = rb_str_intern(str); +#endif + } else { + key = rb_str_new((const char *)stringVal, stringLen); +#ifdef HAVE_RUBY_ENCODING_H + default_internal_enc = rb_default_internal_encoding(); + rb_enc_associate(key, utf8Encoding); + if (default_internal_enc) { + key = rb_str_export_to_enc(key, default_internal_enc); + } +#endif + } + set_key(ctx, key); + return 1; +} + +int end_map_callback(void *ctx) { + end_object(ctx); + return 1; +} + +int start_array_callback(void *ctx) { + start_object(ctx,rb_ary_new()); + return 1; +} + +int end_array_callback(void *ctx) { + end_object(ctx); + return 1; +} + +static yajl_callbacks callbacks = { + null_callback, + boolean_callback, + integer_callback, + double_callback, + number_callback, + string_callback, + start_map_callback, + map_key_callback, + end_map_callback, + start_array_callback, + end_array_callback, +}; + +int get_opts_key(VALUE self, const char *key) { + VALUE opts = rb_iv_get(self, "@opts"); + if (TYPE(opts) != T_HASH) { + rb_raise(rb_eTypeError, "opts is not a valid hash"); + } + return rb_hash_aref(opts, ID2SYM(rb_intern(key))) == Qtrue; +} + +static VALUE mParser_do_yajl_parse(VALUE self, VALUE str, VALUE yajl_opts) { + yajl_handle hand; + yajl_status stat; + unsigned char *err; + volatile CTX ctx; + + rb_ivar_set(self, rb_intern("key"), Qnil); + rb_ivar_set(self, rb_intern("stack"), rb_ary_new()); + rb_ivar_set(self, rb_intern("key_stack"), rb_ary_new()); + + ctx.self = self; + ctx.symbolizeKeys = get_opts_key(self, "symbolize_keys"); + ctx.uniqueKeyChecking = get_opts_key(self, "unique_key_checking"); + + hand = yajl_alloc(&callbacks, NULL, (void *)&ctx); + + if (rb_hash_aref(yajl_opts, ID2SYM(rb_intern("yajl_allow_comments"))) == Qtrue) { + yajl_config(hand, yajl_allow_comments, 1); + } + if (rb_hash_aref(yajl_opts, ID2SYM(rb_intern("yajl_dont_validate_strings"))) == Qtrue) { + yajl_config(hand, yajl_dont_validate_strings, 1); + } + if (rb_hash_aref(yajl_opts, ID2SYM(rb_intern("yajl_allow_trailing_garbage"))) == Qtrue) { + yajl_config(hand, yajl_allow_trailing_garbage, 1); + } + if (rb_hash_aref(yajl_opts, ID2SYM(rb_intern("yajl_allow_multiple_values"))) == Qtrue) { + yajl_config(hand, yajl_allow_multiple_values, 1); + } + if (rb_hash_aref(yajl_opts, ID2SYM(rb_intern("yajl_allow_partial_values"))) == Qtrue) { + yajl_config(hand, yajl_allow_partial_values, 1); + } + + if ((stat = yajl_parse(hand, (unsigned char *)RSTRING_PTR(str), RSTRING_LEN(str))) != yajl_status_ok) { + err = yajl_get_error(hand, 1, (unsigned char *)RSTRING_PTR(str), RSTRING_LEN(str)); + goto raise; + } + if ((stat = yajl_complete_parse(hand)) != yajl_status_ok) { + err = yajl_get_error(hand, 1, (unsigned char *)RSTRING_PTR(str), RSTRING_LEN(str)); + goto raise; + } + yajl_free(hand); + return rb_ary_pop(rb_ivar_get(self, rb_intern("stack"))); + +raise: + if (hand) { + yajl_free(hand); + } + rb_raise(cParseError, "%s", err); +} + +void Init_parser() { + mFFI_Yajl = rb_define_module("FFI_Yajl"); + cParseError = rb_define_class_under(mFFI_Yajl, "ParseError", rb_eStandardError); + mExt = rb_define_module_under(mFFI_Yajl, "Ext"); + mParser = rb_define_module_under(mExt, "Parser"); + rb_define_method(mParser, "do_yajl_parse", mParser_do_yajl_parse, 2); +#ifdef HAVE_RUBY_ENCODING_H + utf8Encoding = rb_utf8_encoding(); +#endif +} |