#include #include #ifdef HAVE_RUBY_ENCODING_H #include static rb_encoding *utf8Encoding; #endif static VALUE mFFI_Yajl, mExt, mParser, cParseError; typedef struct { VALUE finished; VALUE stack; VALUE key_stack; VALUE key; } CTX; void set_value(CTX *ctx, VALUE val) { long len = RARRAY_LEN(ctx->stack); VALUE last = rb_ary_entry(ctx->stack, len-1); switch (TYPE(last)) { case T_ARRAY: rb_ary_push(last, val); break; case T_HASH: rb_hash_aset(last, ctx->key, val); break; default: break; } } void set_key(CTX *ctx, VALUE key) { ctx->key = key; } void start_object(CTX *ctx, VALUE obj) { rb_ary_push(ctx->key_stack, ctx->key); rb_ary_push(ctx->stack, obj); } void end_object(CTX *ctx) { ctx->key = rb_ary_pop(ctx->key_stack); if ( RARRAY_LEN(ctx->stack) > 1 ) { set_value(ctx, rb_ary_pop(ctx->stack)); } else { ctx->finished = rb_ary_pop(ctx->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[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)); } return 1; } int string_callback(void *ctx, const unsigned char *stringVal, size_t stringLen) { char buf[stringLen+1]; VALUE str; #ifdef HAVE_RUBY_ENCODING_H rb_encoding *default_internal_enc; #endif buf[stringLen] = 0; memcpy(buf, stringVal, stringLen); str = rb_str_new2(buf); #ifdef HAVE_RUBY_ENCODING_H 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) { char buf[stringLen+1]; VALUE str; #ifdef HAVE_RUBY_ENCODING_H rb_encoding *default_internal_enc; #endif buf[stringLen] = 0; memcpy(buf, stringVal, stringLen); str = rb_str_new2(buf); #ifdef HAVE_RUBY_ENCODING_H 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_key(ctx,str); 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, }; static VALUE mParser_do_yajl_parse(VALUE self, VALUE str, VALUE opts) { yajl_handle hand; yajl_status stat; unsigned char *err; CTX ctx; ctx.stack = rb_ary_new(); ctx.key_stack = rb_ary_new(); /* hack to avoid garbage collection */ rb_ivar_set(self, rb_intern("stack"), ctx.stack); rb_ivar_set(self, rb_intern("key_stack"), ctx.key_stack); rb_ivar_set(self, rb_intern("finished"), ctx.stack); rb_ivar_set(self, rb_intern("key"), ctx.stack); hand = yajl_alloc(&callbacks, NULL, &ctx); 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 ctx.finished; 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 }