/* dump.c * Copyright (c) 2012, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include "oj.h" #if IVAR_HELPERS #include "ruby/st.h" #else #include "st.h" #endif #include "cache8.h" typedef unsigned long ulong; typedef struct _Out { char *buf; char *end; char *cur; Cache8 circ_cache; slot_t circ_cnt; int indent; int depth; // used by dump_hash Options opts; uint32_t hash_cnt; } *Out; static void dump_obj_to_json(VALUE obj, Options copts, Out out); static void raise_strict(VALUE obj); static void dump_val(VALUE obj, int depth, Out out); static void dump_nil(Out out); static void dump_true(Out out); static void dump_false(Out out); static void dump_fixnum(VALUE obj, Out out); static void dump_bignum(VALUE obj, Out out); static void dump_float(VALUE obj, Out out); static void dump_cstr(const char *str, size_t cnt, int is_sym, int escape1, Out out); static void dump_hex(u_char c, Out out); static void dump_str_comp(VALUE obj, Out out); static void dump_str_obj(VALUE obj, Out out); static void dump_sym_comp(VALUE obj, Out out); static void dump_sym_obj(VALUE obj, Out out); static void dump_class_comp(VALUE obj, Out out); static void dump_class_obj(VALUE obj, Out out); static void dump_array(VALUE obj, int depth, Out out); static int hash_cb_strict(VALUE key, VALUE value, Out out); static int hash_cb_object(VALUE key, VALUE value, Out out); static void dump_hash(VALUE obj, int depth, int mode, Out out); static void dump_time(VALUE obj, Out out); static void dump_data_comp(VALUE obj, Out out); static void dump_data_obj(VALUE obj, Out out); static void dump_obj_comp(VALUE obj, int depth, Out out); static void dump_obj_obj(VALUE obj, int depth, Out out); static void dump_struct_comp(VALUE obj, int depth, Out out); static void dump_struct_obj(VALUE obj, int depth, Out out); static int dump_attr_cb(ID key, VALUE value, Out out); static void dump_obj_attrs(VALUE obj, int with_class, slot_t id, int depth, Out out); static void grow(Out out, size_t len); static int is_json_friendly(const u_char *str, size_t len); static size_t json_friendly_size(const u_char *str, size_t len); static const char hex_chars[17] = "0123456789abcdef"; static char json_friendly_chars[256] = "\ uuuuuuuuxxxuxxuuuuuuuuuuuuuuuuuu\ ooxooooooooooooxoooooooooooooooo\ ooooooooooooooooooooooooooooxooo\ ooooooooooooooooooooooooooooooou\ uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu\ uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu\ uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu\ uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu"; inline static int is_json_friendly(const u_char *str, size_t len) { for (; 0 < len; str++, len--) { if ('o' != json_friendly_chars[*str]) { return 0; } } return 1; } inline static size_t json_friendly_size(const u_char *str, size_t len) { int cnt = 0; for (; 0 < len; str++, len--) { switch (json_friendly_chars[*str]) { case 'o': cnt++; break; case 'x': cnt += 2; break; case 'u': cnt += 6; break; default: break; } } return cnt; } inline static void fill_indent(Out out, int cnt) { if (0 < cnt && 0 < out->indent) { cnt *= out->indent; *out->cur++ = '\n'; for (; 0 < cnt; cnt--) { *out->cur++ = ' '; } } } inline static const char* ulong2str(uint32_t num, char *end) { char *b; *end-- = '\0'; for (b = end; 0 < num || b == end; num /= 10, b--) { *b = (num % 10) + '0'; } b++; return b; } inline static void dump_ulong(unsigned long num, Out out) { char buf[32]; char *b = buf + sizeof(buf) - 1; *b-- = '\0'; if (0 < num) { for (; 0 < num; num /= 10, b--) { *b = (num % 10) + '0'; } b++; } else { *b = '0'; } for (; '\0' != *b; b++) { *out->cur++ = *b; } *out->cur = '\0'; } static void grow(Out out, size_t len) { size_t size = out->end - out->buf; long pos = out->cur - out->buf; char *buf; size *= 2; if (size <= len * 2 + pos) { size += len; } if (0 == (buf = (char*)realloc(out->buf, size + 10))) { // 1 extra for terminator character plus extra (paranoid) rb_raise(rb_eNoMemError, "Failed to create string. [%d:%s]\n", ENOSPC, strerror(ENOSPC)); } out->buf = buf; out->end = buf + size; out->cur = out->buf + pos; } inline static void dump_hex(u_char c, Out out) { u_char d = (c >> 4) & 0x0F; *out->cur++ = hex_chars[d]; d = c & 0x0F; *out->cur++ = hex_chars[d]; } // returns 0 if not using circular references, -1 if not further writing is // needed (duplicate), and a positive value if the object was added to the cache. static long check_circular(VALUE obj, Out out) { slot_t id = 0; slot_t *slot; if (ObjectMode == out->opts->mode && Yes == out->opts->circular) { if (0 == (id = oj_cache8_get(out->circ_cache, obj, &slot))) { out->circ_cnt++; id = out->circ_cnt; *slot = id; } else { if (out->end - out->cur <= 18) { grow(out, 18); } *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = 'r'; dump_ulong(id, out); *out->cur++ = '"'; return -1; } } return (long)id; } static void dump_nil(Out out) { size_t size = 4; if (out->end - out->cur <= (long)size) { grow(out, size); } *out->cur++ = 'n'; *out->cur++ = 'u'; *out->cur++ = 'l'; *out->cur++ = 'l'; *out->cur = '\0'; } static void dump_true(Out out) { size_t size = 4; if (out->end - out->cur <= (long)size) { grow(out, size); } *out->cur++ = 't'; *out->cur++ = 'r'; *out->cur++ = 'u'; *out->cur++ = 'e'; *out->cur = '\0'; } static void dump_false(Out out) { size_t size = 5; if (out->end - out->cur <= (long)size) { grow(out, size); } *out->cur++ = 'f'; *out->cur++ = 'a'; *out->cur++ = 'l'; *out->cur++ = 's'; *out->cur++ = 'e'; *out->cur = '\0'; } static void dump_fixnum(VALUE obj, Out out) { char buf[32]; char *b = buf + sizeof(buf) - 1; long num = NUM2LONG(obj); int neg = 0; if (0 > num) { neg = 1; num = -num; } *b-- = '\0'; if (0 < num) { for (; 0 < num; num /= 10, b--) { *b = (num % 10) + '0'; } if (neg) { *b = '-'; } else { b++; } } else { *b = '0'; } if (out->end - out->cur <= (long)(sizeof(buf) - (b - buf))) { grow(out, sizeof(buf) - (b - buf)); } for (; '\0' != *b; b++) { *out->cur++ = *b; } *out->cur = '\0'; } static void dump_bignum(VALUE obj, Out out) { VALUE rs = rb_big2str(obj, 10); int cnt = (int)RSTRING_LEN(rs); if (out->end - out->cur <= (long)cnt) { grow(out, cnt); } memcpy(out->cur, StringValuePtr(rs), cnt); out->cur += cnt; *out->cur = '\0'; } static void dump_float(VALUE obj, Out out) { char buf[64]; char *b; int cnt = sprintf(buf, "%0.16g", RFLOAT_VALUE(obj)); // used sprintf due to bug in snprintf if (out->end - out->cur <= (long)cnt) { grow(out, cnt); } for (b = buf; '\0' != *b; b++) { *out->cur++ = *b; } *out->cur = '\0'; } static void dump_cstr(const char *str, size_t cnt, int is_sym, int escape1, Out out) { size_t size = json_friendly_size((u_char*)str, cnt); if (out->end - out->cur <= (long)size + 10) { // extra 10 for escaped first char, quotes, and sym grow(out, size + 10); } *out->cur++ = '"'; if (escape1) { *out->cur++ = '\\'; *out->cur++ = 'u'; *out->cur++ = '0'; *out->cur++ = '0'; dump_hex((u_char)*str, out); cnt--; size--; str++; is_sym = 0; // just to make sure } if (cnt == size) { if (is_sym) { *out->cur++ = ':'; } for (; '\0' != *str; str++) { *out->cur++ = *str; } *out->cur++ = '"'; } else { if (is_sym) { *out->cur++ = ':'; } for (; 0 < cnt; cnt--, str++) { switch (json_friendly_chars[(u_char)*str]) { case 'o': *out->cur++ = *str; break; case 'x': *out->cur++ = '\\'; switch (*str) { case '\b': *out->cur++ = 'b'; break; case '\t': *out->cur++ = 't'; break; case '\n': *out->cur++ = 'n'; break; case '\f': *out->cur++ = 'f'; break; case '\r': *out->cur++ = 'r'; break; default: *out->cur++ = *str; break; } break; case 'u': *out->cur++ = '\\'; *out->cur++ = 'u'; if ((u_char)*str <= 0x7F) { *out->cur++ = '0'; *out->cur++ = '0'; dump_hex((u_char)*str, out); } else { // continuation? // TBD lead with \u00 . grab next char? *out->cur++ = '0'; *out->cur++ = '0'; dump_hex((u_char)*str, out); } break; default: break; // ignore, should never happen if the table is correct } } *out->cur++ = '"'; } *out->cur = '\0'; } static void dump_str_comp(VALUE obj, Out out) { dump_cstr(StringValuePtr(obj), RSTRING_LEN(obj), 0, 0, out); } static void dump_str_obj(VALUE obj, Out out) { const char *s = StringValuePtr(obj); size_t len = RSTRING_LEN(obj); char s1 = s[1]; dump_cstr(s, len, 0, (':' == *s || ('^' == *s && ('r' == s1 || 'i' == s1))), out); } static void dump_sym_comp(VALUE obj, Out out) { const char *sym = rb_id2name(SYM2ID(obj)); dump_cstr(sym, strlen(sym), 0, 0, out); } static void dump_sym_obj(VALUE obj, Out out) { const char *sym = rb_id2name(SYM2ID(obj)); size_t len = strlen(sym); dump_cstr(sym, len, 1, 0, out); } static void dump_class_comp(VALUE obj, Out out) { const char *s = rb_class2name(obj); dump_cstr(s, strlen(s), 0, 0, out); } static void dump_class_obj(VALUE obj, Out out) { const char *s = rb_class2name(obj); size_t len = strlen(s); if (out->end - out->cur <= 6) { grow(out, 6); } *out->cur++ = '{'; *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = 'c'; *out->cur++ = '"'; *out->cur++ = ':'; dump_cstr(s, len, 0, 0, out); *out->cur++ = '}'; *out->cur = '\0'; } static void dump_array(VALUE a, int depth, Out out) { VALUE *np; size_t size; int cnt; int d2 = depth + 1; long id = check_circular(a, out); if (id < 0) { return; } np = RARRAY_PTR(a); cnt = (int)RARRAY_LEN(a); *out->cur++ = '['; if (0 < id) { size = d2 * out->indent + 16; if (out->end - out->cur <= (long)size) { grow(out, size); } fill_indent(out, d2); *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = 'i'; dump_ulong(id, out); *out->cur++ = '"'; } size = 2; if (out->end - out->cur <= (long)size) { grow(out, size); } if (0 == cnt) { *out->cur++ = ']'; } else { if (0 < id) { *out->cur++ = ','; } size = d2 * out->indent + 2; for (; 0 < cnt; cnt--, np++) { if (out->end - out->cur <= (long)size) { grow(out, size); } fill_indent(out, d2); dump_val(*np, d2, out); if (1 < cnt) { *out->cur++ = ','; } } size = depth * out->indent + 1; if (out->end - out->cur <= (long)size) { grow(out, size); } fill_indent(out, depth); *out->cur++ = ']'; } *out->cur = '\0'; } static int hash_cb_strict(VALUE key, VALUE value, Out out) { int depth = out->depth; long size = depth * out->indent + 1; if (out->end - out->cur <= size) { grow(out, size); } fill_indent(out, depth); if (rb_type(key) == T_STRING) { dump_str_comp(key, out); *out->cur++ = ':'; dump_val(value, depth, out); } else { rb_raise(rb_eTypeError, "In :strict mode all Hash keys must be Strings."); } out->depth = depth; *out->cur++ = ','; return ST_CONTINUE; } static int hash_cb_object(VALUE key, VALUE value, Out out) { int depth = out->depth; long size = depth * out->indent + 1; if (out->end - out->cur <= size) { grow(out, size); } fill_indent(out, depth); if (rb_type(key) == T_STRING) { dump_str_obj(key, out); *out->cur++ = ':'; dump_val(value, depth, out); } else if (rb_type(key) == T_SYMBOL) { dump_sym_obj(key, out); *out->cur++ = ':'; dump_val(value, depth, out); } else { int d2 = depth + 1; long s2 = size + out->indent + 1; int i; int started = 0; u_char b; if (out->end - out->cur <= s2 + 15) { grow(out, s2 + 15); } *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = '#'; out->hash_cnt++; for (i = 28; 0 <= i; i -= 4) { b = (u_char)((out->hash_cnt >> i) & 0x0000000F); if ('\0' != b) { started = 1; } if (started) { *out->cur++ = hex_chars[b]; } } *out->cur++ = '"'; *out->cur++ = ':'; *out->cur++ = '['; fill_indent(out, d2); dump_val(key, d2, out); if (out->end - out->cur <= (long)s2) { grow(out, s2); } *out->cur++ = ','; fill_indent(out, d2); dump_val(value, d2, out); if (out->end - out->cur <= (long)size) { grow(out, size); } fill_indent(out, depth); *out->cur++ = ']'; } out->depth = depth; *out->cur++ = ','; return ST_CONTINUE; } static void dump_hash(VALUE obj, int depth, int mode, Out out) { int cnt = (int)RHASH_SIZE(obj); size_t size = depth * out->indent + 2; if (out->end - out->cur <= 2) { grow(out, 2); } if (0 == cnt) { *out->cur++ = '{'; *out->cur++ = '}'; } else { long id = check_circular(obj, out); if (0 > id) { return; } *out->cur++ = '{'; if (0 < id) { if (out->end - out->cur <= (long)size + 16) { grow(out, size + 16); } fill_indent(out, depth + 1); *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = 'i'; *out->cur++ = '"'; *out->cur++ = ':'; dump_ulong(id, out); *out->cur++ = ','; } out->depth = depth + 1; if (ObjectMode == mode) { rb_hash_foreach(obj, hash_cb_object, (VALUE)out); } else { rb_hash_foreach(obj, hash_cb_strict, (VALUE)out); } if (',' == *(out->cur - 1)) { out->cur--; // backup to overwrite last comma } if (out->end - out->cur <= (long)size) { grow(out, size); } fill_indent(out, depth); *out->cur++ = '}'; } *out->cur = '\0'; } static void dump_time(VALUE obj, Out out) { char buf[64]; char *b = buf + sizeof(buf) - 1; time_t sec = NUM2LONG(rb_funcall2(obj, oj_tv_sec_id, 0, 0)); long usec = NUM2LONG(rb_funcall2(obj, oj_tv_usec_id, 0, 0)); char *dot = b - 7; long size; *b-- = '\0'; for (; dot < b; b--, usec /= 10) { *b = '0' + (usec % 10); } *b-- = '.'; for (; 0 < sec; b--, sec /= 10) { *b = '0' + (sec % 10); } b++; size = sizeof(buf) - (b - buf) - 1; if (out->end - out->cur <= size) { grow(out, size); } memcpy(out->cur, b, size); out->cur += size; *out->cur = '\0'; } static void dump_data_comp(VALUE obj, Out out) { VALUE clas = rb_obj_class(obj); if (rb_cTime == clas) { dump_time(obj, out); } else { dump_nil(out); } } static void dump_data_obj(VALUE obj, Out out) { VALUE clas = rb_obj_class(obj); if (rb_cTime == clas) { if (out->end - out->cur <= 6) { grow(out, 6); } *out->cur++ = '{'; *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = 't'; *out->cur++ = '"'; *out->cur++ = ':'; dump_time(obj, out); *out->cur++ = '}'; *out->cur = '\0'; } else { dump_nil(out); } } static void dump_obj_comp(VALUE obj, int depth, Out out) { if (rb_respond_to(obj, oj_to_hash_id)) { VALUE h = rb_funcall(obj, oj_to_hash_id, 0); if (T_HASH != rb_type(h)) { rb_raise(rb_eTypeError, "%s.to_hash() did not return a Hash.\n", rb_class2name(rb_obj_class(obj))); } dump_hash(h, depth, out->opts->mode, out); } else if (rb_respond_to(obj, oj_as_json_id)) { VALUE h = rb_funcall(obj, oj_as_json_id, 0); if (T_HASH != rb_type(h)) { rb_raise(rb_eTypeError, "%s.as_json() did not return a Hash.\n", rb_class2name(rb_obj_class(obj))); } dump_hash(h, depth, out->opts->mode, out); } else if (rb_respond_to(obj, oj_to_json_id)) { VALUE rs = rb_funcall(obj, oj_to_json_id, 0); const char *s = StringValuePtr(rs); int len = (int)RSTRING_LEN(rs); if (out->end - out->cur <= len) { grow(out, len); } memcpy(out->cur, s, len); out->cur += len; } else { dump_obj_attrs(obj, 0, 0, depth, out); } *out->cur = '\0'; } inline static void dump_obj_obj(VALUE obj, int depth, Out out) { long id = check_circular(obj, out); if (0 <= id) { dump_obj_attrs(obj, 1, id, depth, out); } } static int dump_attr_cb(ID key, VALUE value, Out out) { int depth = out->depth; size_t size = depth * out->indent + 1; const char *attr = rb_id2name(key); if (out->end - out->cur <= (long)size) { grow(out, size); } fill_indent(out, depth); if ('@' == *attr) { attr++; dump_cstr(attr, strlen(attr), 0, 0, out); } else { char buf[32]; *buf = '~'; strncpy(buf + 1, attr, sizeof(buf) - 2); buf[sizeof(buf) - 1] = '\0'; dump_cstr(buf, strlen(buf), 0, 0, out); } *out->cur++ = ':'; dump_val(value, depth, out); out->depth = depth; *out->cur++ = ','; return ST_CONTINUE; } static void dump_obj_attrs(VALUE obj, int with_class, slot_t id, int depth, Out out) { size_t size; int d2 = depth + 1; if (out->end - out->cur <= 2) { grow(out, 2); } *out->cur++ = '{'; if (with_class) { const char *class_name = rb_class2name(rb_obj_class(obj)); int clen = (int)strlen(class_name); size = d2 * out->indent + clen + 10; if (out->end - out->cur <= (long)size) { grow(out, size); } fill_indent(out, d2); *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = 'o'; *out->cur++ = '"'; *out->cur++ = ':'; dump_cstr(class_name, clen, 0, 0, out); } if (0 < id) { size = d2 * out->indent + 16; if (out->end - out->cur <= (long)size) { grow(out, size); } *out->cur++ = ','; fill_indent(out, d2); *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = 'i'; *out->cur++ = '"'; *out->cur++ = ':'; dump_ulong(id, out); } { int cnt; // use encoding as the indicator for Ruby 1.8.7 or 1.9.x #if IVAR_HELPERS cnt = (int)rb_ivar_count(obj); #else VALUE vars = rb_funcall2(obj, oj_instance_variables_id, 0, 0); VALUE *np = RARRAY_PTR(vars); ID vid; const char *attr; int i; cnt = (int)RARRAY_LEN(vars); #endif if (with_class && 0 < cnt) { *out->cur++ = ','; } out->depth = depth + 1; #if IVAR_HELPERS rb_ivar_foreach(obj, dump_attr_cb, (VALUE)out); out->cur--; // backup to overwrite last comma #else size = d2 * out->indent + 1; for (i = cnt; 0 < i; i--, np++) { if (out->end - out->cur <= (long)size) { grow(out, size); } vid = rb_to_id(*np); fill_indent(out, d2); attr = rb_id2name(vid); if ('@' == *attr) { attr++; dump_cstr(attr, strlen(attr), 0, 0, out); } else { char buf[32]; *buf = '~'; strncpy(buf + 1, attr, sizeof(buf) - 2); buf[sizeof(buf) - 1] = '\0'; dump_cstr(buf, strlen(attr) + 1, 0, 0, out); } *out->cur++ = ':'; dump_val(rb_ivar_get(obj, vid), d2, out); if (out->end - out->cur <= 2) { grow(out, 2); } if (1 < i) { *out->cur++ = ','; } } #endif out->depth = depth; } *out->cur++ = '}'; *out->cur = '\0'; } static void dump_struct_comp(VALUE obj, int depth, Out out) { if (rb_respond_to(obj, oj_to_hash_id)) { VALUE h = rb_funcall(obj, oj_to_hash_id, 0); if (T_HASH != rb_type(h)) { rb_raise(rb_eTypeError, "%s.to_hash() did not return a Hash.\n", rb_class2name(rb_obj_class(obj))); } dump_hash(h, depth, out->opts->mode, out); } else if (rb_respond_to(obj, oj_to_json_id)) { VALUE rs = rb_funcall(obj, oj_to_json_id, 0); const char *s = StringValuePtr(rs); int len = (int)RSTRING_LEN(rs); if (out->end - out->cur <= len) { grow(out, len); } memcpy(out->cur, s, len); out->cur += len; } else { dump_struct_obj(obj, depth, out); } } static void dump_struct_obj(VALUE obj, int depth, Out out) { VALUE clas = rb_obj_class(obj); const char *class_name = rb_class2name(clas); VALUE *vp; int i; int d2 = depth + 1; int d3 = d2 + 1; size_t len = strlen(class_name); size_t size = d2 * out->indent + d3 * out->indent + 10 + len; if (out->end - out->cur <= (long)size) { grow(out, size); } *out->cur++ = '{'; fill_indent(out, d2); *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = 'u'; *out->cur++ = '"'; *out->cur++ = ':'; *out->cur++ = '['; fill_indent(out, d3); *out->cur++ = '"'; memcpy(out->cur, class_name, len); out->cur += len; *out->cur++ = '"'; *out->cur++ = ','; size = d3 * out->indent + 2; for (i = (int)RSTRUCT_LEN(obj), vp = RSTRUCT_PTR(obj); 0 < i; i--, vp++) { if (out->end - out->cur <= (long)size) { grow(out, size); } fill_indent(out, d3); dump_val(*vp, d3, out); *out->cur++ = ','; } out->cur--; *out->cur++ = ']'; *out->cur++ = '}'; *out->cur = '\0'; } static void raise_strict(VALUE obj) { rb_raise(rb_eTypeError, "Failed to dump %s Object to JSON in strict mode.\n", rb_class2name(rb_obj_class(obj))); } static void dump_val(VALUE obj, int depth, Out out) { switch (rb_type(obj)) { case T_NIL: dump_nil(out); break; case T_TRUE: dump_true(out); break; case T_FALSE: dump_false(out); break; case T_FIXNUM: dump_fixnum(obj, out); break; case T_FLOAT: dump_float(obj, out); break; case T_BIGNUM: dump_bignum(obj, out); break; case T_STRING: switch (out->opts->mode) { case StrictMode: case NullMode: case CompatMode: dump_str_comp(obj, out); break; case ObjectMode: default: dump_str_obj(obj, out); break; } break; case T_SYMBOL: switch (out->opts->mode) { case StrictMode: raise_strict(obj); break; case NullMode: dump_nil(out); break; case CompatMode: dump_sym_comp(obj, out); break; case ObjectMode: default: dump_sym_obj(obj, out); break; } break; case T_ARRAY: dump_array(obj, depth, out); break; case T_HASH: dump_hash(obj, depth, out->opts->mode, out); break; case T_CLASS: switch (out->opts->mode) { case StrictMode: raise_strict(obj); break; case NullMode: dump_nil(out); break; case CompatMode: dump_class_comp(obj, out); break; case ObjectMode: default: dump_class_obj(obj, out); break; } break; case T_OBJECT: switch (out->opts->mode) { case StrictMode: raise_strict(obj); break; case NullMode: dump_nil(out); break; case CompatMode: dump_obj_comp(obj, depth, out); break; case ObjectMode: default: dump_obj_obj(obj, depth, out); break; } break; case T_DATA: switch (out->opts->mode) { case StrictMode: raise_strict(obj); break; case NullMode: dump_nil(out); break; case CompatMode: dump_data_comp(obj, out); break; case ObjectMode: default: dump_data_obj(obj, out); break; } break; case T_STRUCT: // for Range switch (out->opts->mode) { case StrictMode: raise_strict(obj); break; case NullMode: dump_nil(out); break; case CompatMode: dump_struct_comp(obj, depth, out); break; case ObjectMode: default: dump_struct_obj(obj, depth, out); break; } break; #if (defined T_COMPLEX && defined RCOMPLEX) case T_COMPLEX: #endif #if (defined T_RATIONAL && defined RRATIONAL) case T_RATIONAL: #endif case T_REGEXP: switch (out->opts->mode) { case StrictMode: raise_strict(obj); break; case NullMode: dump_nil(out); break; case CompatMode: case ObjectMode: default: dump_obj_comp(obj, depth, out); break; } break; default: rb_raise(rb_eNotImpError, "Failed to dump '%s' Object (%02x)\n", rb_class2name(rb_obj_class(obj)), rb_type(obj)); break; } } static void dump_obj_to_json(VALUE obj, Options copts, Out out) { out->buf = (char*)malloc(65336); out->end = out->buf + 65325; // 1 less than end plus extra for possible errors out->cur = out->buf; out->circ_cnt = 0; out->opts = copts; out->hash_cnt = 0; if (Yes == copts->circular) { oj_cache8_new(&out->circ_cache); } out->indent = copts->indent; dump_val(obj, 0, out); if (Yes == copts->circular) { oj_cache8_delete(out->circ_cache); } } char* oj_write_obj_to_str(VALUE obj, Options copts) { struct _Out out; dump_obj_to_json(obj, copts, &out); return out.buf; } void oj_write_obj_to_file(VALUE obj, const char *path, Options copts) { struct _Out out; size_t size; FILE *f; dump_obj_to_json(obj, copts, &out); size = out.cur - out.buf; if (0 == (f = fopen(path, "w"))) { rb_raise(rb_eIOError, "%s\n", strerror(errno)); } if (size != fwrite(out.buf, 1, size, f)) { int err = ferror(f); rb_raise(rb_eIOError, "Write failed. [%d:%s]\n", err, strerror(err)); } free(out.buf); fclose(f); }