Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

2531 lines (2332 sloc) 62.506 kb
/* 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 <stdlib.h>
#include <errno.h>
#if !IS_WINDOWS
#include <sys/time.h>
#endif
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <unistd.h>
#include <errno.h>
#include "oj.h"
#include "cache8.h"
#include "odd.h"
#if !HAS_ENCODING_SUPPORT || defined(RUBINIUS_RUBY)
#define rb_eEncodingError rb_eException
#endif
// Workaround in case INFINITY is not defined in math.h or if the OS is CentOS
#define OJ_INFINITY (1.0/0.0)
// Extra padding at end of buffer.
#define BUFFER_EXTRA 10
#define MAX_DEPTH 1000
typedef unsigned long ulong;
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_raw(const char *str, size_t cnt, Out out);
static void dump_cstr(const char *str, size_t cnt, int is_sym, int escape1, Out out);
static void dump_hex(uint8_t c, Out out);
static void dump_str_comp(VALUE obj, Out out);
static void dump_str_obj(VALUE obj, VALUE clas, int depth, 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, VALUE clas, int depth, Out out);
static int hash_cb_strict(VALUE key, VALUE value, Out out);
static int hash_cb_compat(VALUE key, VALUE value, Out out);
static int hash_cb_object(VALUE key, VALUE value, Out out);
static void dump_hash(VALUE obj, VALUE clas, int depth, int mode, Out out);
static void dump_time(VALUE obj, Out out, int withZone);
static void dump_ruby_time(VALUE obj, Out out);
static void dump_xml_time(VALUE obj, Out out);
static void dump_data_strict(VALUE obj, Out out);
static void dump_data_null(VALUE obj, Out out);
static void dump_data_comp(VALUE obj, int depth, Out out);
static void dump_data_obj(VALUE obj, int depth, 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);
#if HAS_IVAR_HELPERS
static int dump_attr_cb(ID key, VALUE value, Out out);
#endif
static void dump_obj_attrs(VALUE obj, VALUE clas, slot_t id, int depth, Out out);
static void dump_odd(VALUE obj, Odd odd, VALUE clas, int depth, Out out);
static void grow(Out out, size_t len);
static size_t hibit_friendly_size(const uint8_t *str, size_t len);
static size_t xss_friendly_size(const uint8_t *str, size_t len);
static size_t ascii_friendly_size(const uint8_t *str, size_t len);
static void dump_leaf(Leaf leaf, int depth, Out out);
static void dump_leaf_str(Leaf leaf, Out out);
static void dump_leaf_fixnum(Leaf leaf, Out out);
static void dump_leaf_float(Leaf leaf, Out out);
static void dump_leaf_array(Leaf leaf, int depth, Out out);
static void dump_leaf_hash(Leaf leaf, int depth, Out out);
static const char hex_chars[17] = "0123456789abcdef";
// JSON standard except newlines are no escaped
static char newline_friendly_chars[256] = "\
66666666221622666666666666666666\
11211111111111111111111111111111\
11111111111111111111111111112111\
11111111111111111111111111111111\
11111111111111111111111111111111\
11111111111111111111111111111111\
11111111111111111111111111111111\
11111111111111111111111111111111";
// JSON standard
static char hibit_friendly_chars[256] = "\
66666666222622666666666666666666\
11211111111111111111111111111111\
11111111111111111111111111112111\
11111111111111111111111111111111\
11111111111111111111111111111111\
11111111111111111111111111111111\
11111111111111111111111111111111\
11111111111111111111111111111111";
// High bit set characters are always encoded as unicode. Worse case is 3
// bytes per character in the output. That makes this conservative.
static char ascii_friendly_chars[256] = "\
66666666222622666666666666666666\
11211111111111111111111111111111\
11111111111111111111111111112111\
11111111111111111111111111111116\
33333333333333333333333333333333\
33333333333333333333333333333333\
33333333333333333333333333333333\
33333333333333333333333333333333";
// XSS safe mode
static char xss_friendly_chars[256] = "\
66666666222622666666666666666666\
11211161111111121111111111116161\
11111111111111111111111111112111\
11111111111111111111111111111116\
33333333333333333333333333333333\
33333333333333333333333333333333\
33333333333333333333333333333333\
33333333333333333333333333333333";
inline static size_t
newline_friendly_size(const uint8_t *str, size_t len) {
size_t size = 0;
for (; 0 < len; str++, len--) {
size += newline_friendly_chars[*str];
}
return size - len * (size_t)'0';
}
inline static size_t
hibit_friendly_size(const uint8_t *str, size_t len) {
size_t size = 0;
for (; 0 < len; str++, len--) {
size += hibit_friendly_chars[*str];
}
return size - len * (size_t)'0';
}
inline static size_t
ascii_friendly_size(const uint8_t *str, size_t len) {
size_t size = 0;
for (; 0 < len; str++, len--) {
size += ascii_friendly_chars[*str];
}
return size - len * (size_t)'0';
}
inline static size_t
xss_friendly_size(const uint8_t *str, size_t len) {
size_t size = 0;
for (; 0 < len; str++, len--) {
size += xss_friendly_chars[*str];
}
return size - len * (size_t)'0';
}
inline static void
fill_indent(Out out, int cnt) {
if (0 < out->indent) {
cnt *= out->indent;
*out->cur++ = '\n';
for (; 0 < cnt; cnt--) {
*out->cur++ = ' ';
}
}
}
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 (out->allocated) {
buf = REALLOC_N(out->buf, char, (size + BUFFER_EXTRA));
} else {
buf = ALLOC_N(char, (size + BUFFER_EXTRA));
out->allocated = 1;
memcpy(buf, out->buf, out->end - out->buf + BUFFER_EXTRA);
}
if (0 == buf) {
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(uint8_t c, Out out) {
uint8_t d = (c >> 4) & 0x0F;
*out->cur++ = hex_chars[d];
d = c & 0x0F;
*out->cur++ = hex_chars[d];
}
static void
dump_raw(const char *str, size_t cnt, Out out) {
if (out->end - out->cur <= (long)cnt + 10) {
grow(out, cnt + 10);
}
memcpy(out->cur, str, cnt);
out->cur += cnt;
*out->cur = '\0';
}
const char*
dump_unicode(const char *str, const char *end, Out out) {
uint32_t code = 0;
uint8_t b = *(uint8_t*)str;
int i, cnt;
if (0xC0 == (0xE0 & b)) {
cnt = 1;
code = b & 0x0000001F;
} else if (0xE0 == (0xF0 & b)) {
cnt = 2;
code = b & 0x0000000F;
} else if (0xF0 == (0xF8 & b)) {
cnt = 3;
code = b & 0x00000007;
} else if (0xF8 == (0xFC & b)) {
cnt = 4;
code = b & 0x00000003;
} else if (0xFC == (0xFE & b)) {
cnt = 5;
code = b & 0x00000001;
} else {
cnt = 0;
rb_raise(rb_eEncodingError, "Invalid Unicode\n");
}
str++;
for (; 0 < cnt; cnt--, str++) {
b = *(uint8_t*)str;
if (end <= str || 0x80 != (0xC0 & b)) {
rb_raise(rb_eEncodingError, "Invalid Unicode\n");
}
code = (code << 6) | (b & 0x0000003F);
}
if (0x0000FFFF < code) {
uint32_t c1;
code -= 0x00010000;
c1 = ((code >> 10) & 0x000003FF) + 0x0000D800;
code = (code & 0x000003FF) + 0x0000DC00;
*out->cur++ = '\\';
*out->cur++ = 'u';
for (i = 3; 0 <= i; i--) {
*out->cur++ = hex_chars[(uint8_t)(c1 >> (i * 4)) & 0x0F];
}
}
*out->cur++ = '\\';
*out->cur++ = 'u';
for (i = 3; 0 <= i; i--) {
*out->cur++ = hex_chars[(uint8_t)(code >> (i * 4)) & 0x0F];
}
return str - 1;
}
// 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) {
volatile 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, rb_string_value_ptr((VALUE*)&rs), cnt);
out->cur += cnt;
*out->cur = '\0';
}
// Removed dependencies on math due to problems with CentOS 5.4.
static void
dump_float(VALUE obj, Out out) {
char buf[64];
char *b;
double d = rb_num2dbl(obj);
int cnt;
if (0.0 == d) {
b = buf;
*b++ = '0';
*b++ = '.';
*b++ = '0';
*b++ = '\0';
cnt = 3;
} else if (OJ_INFINITY == d) {
switch (out->opts->mode) {
case StrictMode:
raise_strict(obj);
case NullMode:
strcpy(buf, "null");
cnt = 4;
break;
default:
strcpy(buf, "Infinity");
cnt = 8;
break;
}
} else if (-OJ_INFINITY == d) {
switch (out->opts->mode) {
case StrictMode:
raise_strict(obj);
case NullMode:
strcpy(buf, "null");
cnt = 4;
break;
default:
strcpy(buf, "-Infinity");
cnt = 9;
break;
}
} else if (isnan(d)) {
switch (out->opts->mode) {
case StrictMode:
raise_strict(obj);
case NullMode:
strcpy(buf, "null");
cnt = 4;
break;
default:
strcpy(buf, "NaN");
cnt = 3;
break;
}
} else if (d == (double)(long long int)d) {
cnt = snprintf(buf, sizeof(buf), "%.1f", d);
} else if (0 == out->opts->float_prec) {
volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
cnt = RSTRING_LEN(rstr);
if ((int)sizeof(buf) <= cnt) {
cnt = sizeof(buf) - 1;
}
strncpy(buf, rb_string_value_ptr((VALUE*)&rstr), cnt);
buf[cnt] = '\0';
} else {
cnt = snprintf(buf, sizeof(buf), out->opts->float_fmt, d);
}
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;
char *cmap;
switch (out->opts->escape_mode) {
case NLEsc:
cmap = newline_friendly_chars;
size = newline_friendly_size((uint8_t*)str, cnt);
break;
case ASCIIEsc:
cmap = ascii_friendly_chars;
size = ascii_friendly_size((uint8_t*)str, cnt);
break;
case XSSEsc:
cmap = xss_friendly_chars;
size = xss_friendly_size((uint8_t*)str, cnt);
break;
case JSONEsc:
default:
cmap = hibit_friendly_chars;
size = hibit_friendly_size((uint8_t*)str, cnt);
}
if (out->end - out->cur <= (long)size + BUFFER_EXTRA) { // extra 10 for escaped first char, quotes, and sym
grow(out, size + BUFFER_EXTRA);
}
*out->cur++ = '"';
if (escape1) {
*out->cur++ = '\\';
*out->cur++ = 'u';
*out->cur++ = '0';
*out->cur++ = '0';
dump_hex((uint8_t)*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 {
const char *end = str + cnt;
if (is_sym) {
*out->cur++ = ':';
}
for (; str < end; str++) {
switch (cmap[(uint8_t)*str]) {
case '1':
*out->cur++ = *str;
break;
case '2':
*out->cur++ = '\\';
switch (*str) {
case '\\': *out->cur++ = '\\'; break;
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 '3': // Unicode
str = dump_unicode(str, end, out);
break;
case '6': // control characters
*out->cur++ = '\\';
*out->cur++ = 'u';
*out->cur++ = '0';
*out->cur++ = '0';
dump_hex((uint8_t)*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(rb_string_value_ptr((VALUE*)&obj), RSTRING_LEN(obj), 0, 0, out);
}
static void
dump_str_obj(VALUE obj, VALUE clas, int depth, Out out) {
if (Qundef != clas && rb_cString != clas) {
dump_obj_attrs(obj, clas, 0, depth, out);
} else {
const char *s = rb_string_value_ptr((VALUE*)&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, VALUE clas, int depth, Out out) {
size_t size;
int i, cnt;
int d2 = depth + 1;
long id = check_circular(a, out);
if (id < 0) {
return;
}
if (Qundef != clas && rb_cArray != clas && ObjectMode == out->opts->mode) {
dump_obj_attrs(a, clas, 0, depth, out);
return;
}
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++ = ',';
}
if (0 == out->opts->dump_opts) {
size = d2 * out->indent + 2;
} else {
size = d2 * out->opts->dump_opts->indent_size + out->opts->dump_opts->array_size + 1;
}
cnt--;
for (i = 0; i <= cnt; i++) {
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
if (0 == out->opts->dump_opts) {
fill_indent(out, d2);
} else {
if (0 < out->opts->dump_opts->array_size) {
strcpy(out->cur, out->opts->dump_opts->array_nl);
out->cur += out->opts->dump_opts->array_size;
}
if (0 < out->opts->dump_opts->indent_size) {
int i;
for (i = d2; 0 < i; i--) {
strcpy(out->cur, out->opts->dump_opts->indent);
out->cur += out->opts->dump_opts->indent_size;
}
}
}
dump_val(rb_ary_entry(a, i), d2, out);
if (i < cnt) {
*out->cur++ = ',';
}
}
size = depth * out->indent + 1;
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
if (0 == out->opts->dump_opts) {
fill_indent(out, depth);
} else {
//printf("*** d2: %u indent: %u '%s'\n", d2, out->opts->dump_opts->indent_size, out->opts->dump_opts->indent);
if (0 < out->opts->dump_opts->array_size) {
strcpy(out->cur, out->opts->dump_opts->array_nl);
out->cur += out->opts->dump_opts->array_size;
}
if (0 < out->opts->dump_opts->indent_size) {
int i;
for (i = depth; 0 < i; i--) {
strcpy(out->cur, out->opts->dump_opts->indent);
out->cur += out->opts->dump_opts->indent_size;
}
}
}
*out->cur++ = ']';
}
*out->cur = '\0';
}
static int
hash_cb_strict(VALUE key, VALUE value, Out out) {
int depth = out->depth;
long size;
if (rb_type(key) != T_STRING) {
rb_raise(rb_eTypeError, "In :strict mode all Hash keys must be Strings, not %s.\n", rb_class2name(rb_obj_class(key)));
}
if (0 == out->opts->dump_opts) {
size = depth * out->indent + 1;
if (out->end - out->cur <= size) {
grow(out, size);
}
fill_indent(out, depth);
dump_str_comp(key, out);
*out->cur++ = ':';
} else {
size = depth * out->opts->dump_opts->indent_size + out->opts->dump_opts->hash_size + 1;
if (out->end - out->cur <= size) {
grow(out, size);
}
if (0 < out->opts->dump_opts->hash_size) {
strcpy(out->cur, out->opts->dump_opts->hash_nl);
out->cur += out->opts->dump_opts->hash_size;
}
if (0 < out->opts->dump_opts->indent_size) {
int i;
for (i = depth; 0 < i; i--) {
strcpy(out->cur, out->opts->dump_opts->indent);
out->cur += out->opts->dump_opts->indent_size;
}
}
dump_str_comp(key, out);
size = out->opts->dump_opts->before_size + out->opts->dump_opts->after_size + 2;
if (out->end - out->cur <= size) {
grow(out, size);
}
if (0 < out->opts->dump_opts->before_size) {
strcpy(out->cur, out->opts->dump_opts->before_sep);
out->cur += out->opts->dump_opts->before_size;
}
*out->cur++ = ':';
if (0 < out->opts->dump_opts->after_size) {
strcpy(out->cur, out->opts->dump_opts->after_sep);
out->cur += out->opts->dump_opts->after_size;
}
}
dump_val(value, depth, out);
out->depth = depth;
*out->cur++ = ',';
return ST_CONTINUE;
}
static int
hash_cb_compat(VALUE key, VALUE value, Out out) {
int depth = out->depth;
long size;
if (0 == out->opts->dump_opts) {
size = depth * out->indent + 1;
if (out->end - out->cur <= size) {
grow(out, size);
}
fill_indent(out, depth);
} else {
size = depth * out->opts->dump_opts->indent_size + out->opts->dump_opts->hash_size + 1;
if (out->end - out->cur <= size) {
grow(out, size);
}
if (0 < out->opts->dump_opts->hash_size) {
strcpy(out->cur, out->opts->dump_opts->hash_nl);
out->cur += out->opts->dump_opts->hash_size;
}
if (0 < out->opts->dump_opts->indent_size) {
int i;
for (i = depth; 0 < i; i--) {
strcpy(out->cur, out->opts->dump_opts->indent);
out->cur += out->opts->dump_opts->indent_size;
}
}
}
switch (rb_type(key)) {
case T_STRING:
dump_str_comp(key, out);
break;
case T_SYMBOL:
dump_sym_comp(key, out);
break;
default:
/*rb_raise(rb_eTypeError, "In :compat mode all Hash keys must be Strings or Symbols, not %s.\n", rb_class2name(rb_obj_class(key)));*/
dump_str_comp(rb_funcall(key, oj_to_s_id, 0), out);
break;
}
if (0 == out->opts->dump_opts) {
*out->cur++ = ':';
} else {
size = out->opts->dump_opts->before_size + out->opts->dump_opts->after_size + 2;
if (out->end - out->cur <= size) {
grow(out, size);
}
if (0 < out->opts->dump_opts->before_size) {
strcpy(out->cur, out->opts->dump_opts->before_sep);
out->cur += out->opts->dump_opts->before_size;
}
*out->cur++ = ':';
if (0 < out->opts->dump_opts->after_size) {
strcpy(out->cur, out->opts->dump_opts->after_sep);
out->cur += out->opts->dump_opts->after_size;
}
}
dump_val(value, depth, out);
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, Qundef, depth, 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;
uint8_t 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 = (uint8_t)((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, VALUE clas, int depth, int mode, Out out) {
int cnt;
size_t size;
if (Qundef != clas && rb_cHash != clas && ObjectMode == mode) {
dump_obj_attrs(obj, clas, 0, depth, out);
return;
}
cnt = (int)RHASH_SIZE(obj);
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 if (CompatMode == mode) {
rb_hash_foreach(obj, hash_cb_compat, (VALUE)out);
} else {
rb_hash_foreach(obj, hash_cb_strict, (VALUE)out);
}
if (',' == *(out->cur - 1)) {
out->cur--; // backup to overwrite last comma
}
if (0 == out->opts->dump_opts) {
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
fill_indent(out, depth);
} else {
size = depth * out->opts->dump_opts->indent_size + out->opts->dump_opts->hash_size + 1;
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
if (0 < out->opts->dump_opts->hash_size) {
strcpy(out->cur, out->opts->dump_opts->hash_nl);
out->cur += out->opts->dump_opts->hash_size;
}
if (0 < out->opts->dump_opts->indent_size) {
int i;
for (i = depth; 0 < i; i--) {
strcpy(out->cur, out->opts->dump_opts->indent);
out->cur += out->opts->dump_opts->indent_size;
}
}
}
*out->cur++ = '}';
}
*out->cur = '\0';
}
static void
dump_time(VALUE obj, Out out, int withZone) {
char buf[64];
char *b = buf + sizeof(buf) - 1;
long size;
char *dot;
int neg = 0;
long one = 1000000000;
#if HAS_RB_TIME_TIMESPEC
struct timespec ts = rb_time_timespec(obj);
time_t sec = ts.tv_sec;
long nsec = ts.tv_nsec;
#else
time_t sec = NUM2LONG(rb_funcall2(obj, oj_tv_sec_id, 0, 0));
#if HAS_NANO_TIME
long nsec = NUM2LONG(rb_funcall2(obj, oj_tv_nsec_id, 0, 0));
#else
long nsec = NUM2LONG(rb_funcall2(obj, oj_tv_usec_id, 0, 0)) * 1000;
#endif
#endif
*b-- = '\0';
if (withZone) {
long tzsecs = NUM2LONG(rb_funcall2(obj, oj_utc_offset_id, 0, 0));
int zneg = (0 > tzsecs);
if (0 == tzsecs && Qtrue == rb_funcall2(obj, oj_utcq_id, 0, 0)) {
tzsecs = 86400;
}
if (zneg) {
tzsecs = -tzsecs;
}
if (0 == tzsecs) {
*b-- = '0';
} else {
for (; 0 < tzsecs; b--, tzsecs /= 10) {
*b = '0' + (tzsecs % 10);
}
if (zneg) {
*b-- = '-';
}
}
*b-- = 'e';
}
if (0 > sec) {
neg = 1;
sec = -sec;
if (0 < nsec) {
nsec = 1000000000 - nsec;
sec--;
}
}
dot = b - 9;
if (0 < out->opts->sec_prec) {
if (9 > out->opts->sec_prec) {
int i;
for (i = 9 - out->opts->sec_prec; 0 < i; i--) {
dot++;
nsec = (nsec + 5) / 10;
one /= 10;
}
}
if (one <= nsec) {
nsec -= one;
sec++;
}
for (; dot < b; b--, nsec /= 10) {
*b = '0' + (nsec % 10);
}
*b-- = '.';
}
if (0 == sec) {
*b-- = '0';
} else {
for (; 0 < sec; b--, sec /= 10) {
*b = '0' + (sec % 10);
}
}
if (neg) {
*b-- = '-';
}
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_ruby_time(VALUE obj, Out out) {
volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), 0, 0, out);
}
static void
dump_xml_time(VALUE obj, Out out) {
char buf[64];
struct tm *tm;
long one = 1000000000;
#if HAS_RB_TIME_TIMESPEC
struct timespec ts = rb_time_timespec(obj);
time_t sec = ts.tv_sec;
long nsec = ts.tv_nsec;
#else
time_t sec = NUM2LONG(rb_funcall2(obj, oj_tv_sec_id, 0, 0));
#if HAS_NANO_TIME
long nsec = NUM2LONG(rb_funcall2(obj, oj_tv_nsec_id, 0, 0));
#else
long nsec = NUM2LONG(rb_funcall2(obj, oj_tv_usec_id, 0, 0)) * 1000;
#endif
#endif
long tzsecs = NUM2LONG(rb_funcall2(obj, oj_utc_offset_id, 0, 0));
int tzhour, tzmin;
char tzsign = '+';
if (out->end - out->cur <= 36) {
grow(out, 36);
}
if (9 > out->opts->sec_prec) {
int i;
for (i = 9 - out->opts->sec_prec; 0 < i; i--) {
nsec = (nsec + 5) / 10;
one /= 10;
}
if (one <= nsec) {
nsec -= one;
sec++;
}
}
// 2012-01-05T23:58:07.123456000+09:00
//tm = localtime(&sec);
sec += tzsecs;
tm = gmtime(&sec);
#if 1
if (0 > tzsecs) {
tzsign = '-';
tzhour = (int)(tzsecs / -3600);
tzmin = (int)(tzsecs / -60) - (tzhour * 60);
} else {
tzhour = (int)(tzsecs / 3600);
tzmin = (int)(tzsecs / 60) - (tzhour * 60);
}
#else
if (0 > tm->tm_gmtoff) {
tzsign = '-';
tzhour = (int)(tm->tm_gmtoff / -3600);
tzmin = (int)(tm->tm_gmtoff / -60) - (tzhour * 60);
} else {
tzhour = (int)(tm->tm_gmtoff / 3600);
tzmin = (int)(tm->tm_gmtoff / 60) - (tzhour * 60);
}
#endif
if (0 == nsec || 0 == out->opts->sec_prec) {
if (0 == tzsecs && Qtrue == rb_funcall2(obj, oj_utcq_id, 0, 0)) {
sprintf(buf, "%04d-%02d-%02dT%02d:%02d:%02dZ",
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec);
dump_cstr(buf, 20, 0, 0, out);
} else {
sprintf(buf, "%04d-%02d-%02dT%02d:%02d:%02d%c%02d:%02d",
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec,
tzsign, tzhour, tzmin);
dump_cstr(buf, 25, 0, 0, out);
}
} else if (0 == tzsecs && Qtrue == rb_funcall2(obj, oj_utcq_id, 0, 0)) {
char format[64] = "%04d-%02d-%02dT%02d:%02d:%02d.%09ldZ";
int len = 30;
if (9 > out->opts->sec_prec) {
format[32] = '0' + out->opts->sec_prec;
len -= 9 - out->opts->sec_prec;
}
sprintf(buf, format,
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec, nsec);
dump_cstr(buf, len, 0, 0, out);
} else {
char format[64] = "%04d-%02d-%02dT%02d:%02d:%02d.%09ld%c%02d:%02d";
int len = 35;
if (9 > out->opts->sec_prec) {
format[32] = '0' + out->opts->sec_prec;
len -= 9 - out->opts->sec_prec;
}
sprintf(buf, format,
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec, nsec,
tzsign, tzhour, tzmin);
dump_cstr(buf, len, 0, 0, out);
}
}
static void
dump_data_strict(VALUE obj, Out out) {
VALUE clas = rb_obj_class(obj);
if (oj_bigdecimal_class == clas) {
volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
dump_raw(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), out);
} else {
raise_strict(obj);
}
}
static void
dump_data_null(VALUE obj, Out out) {
VALUE clas = rb_obj_class(obj);
if (oj_bigdecimal_class == clas) {
volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
dump_raw(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), out);
} else {
dump_nil(out);
}
}
static void
dump_data_comp(VALUE obj, int depth, Out out) {
VALUE clas = rb_obj_class(obj);
if (rb_respond_to(obj, oj_to_hash_id)) {
volatile 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, Qundef, depth, out->opts->mode, out);
} else if (Yes == out->opts->bigdec_as_num && oj_bigdecimal_class == clas) {
volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
dump_raw(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), out);
} else if (rb_respond_to(obj, oj_as_json_id)) {
volatile VALUE aj = rb_funcall(obj, oj_as_json_id, 0);
// Catch the obvious brain damaged recursive dumping.
if (aj == obj) {
volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), 0, 0, out);
} else {
dump_val(aj, depth, out);
}
} else if (Yes == out->opts->to_json && rb_respond_to(obj, oj_to_json_id)) {
volatile VALUE rs;
const char *s;
int len;
rs = rb_funcall(obj, oj_to_json_id, 0);
s = rb_string_value_ptr((VALUE*)&rs);
len = (int)RSTRING_LEN(rs);
if (out->end - out->cur <= len + 1) {
grow(out, len);
}
memcpy(out->cur, s, len);
out->cur += len;
*out->cur = '\0';
} else {
if (rb_cTime == clas) {
switch (out->opts->time_format) {
case RubyTime: dump_ruby_time(obj, out); break;
case XmlTime: dump_xml_time(obj, out); break;
case UnixZTime: dump_time(obj, out, 1); break;
case UnixTime:
default: dump_time(obj, out, 0); break;
}
} else if (oj_bigdecimal_class == clas) {
volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), 0, 0, out);
} else {
volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), 0, 0, out);
}
}
}
static void
dump_data_obj(VALUE obj, int depth, 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++ = ':';
switch (out->opts->time_format) {
case RubyTime: // Does not output fractional seconds
case XmlTime:
dump_xml_time(obj, out);
break;
case UnixZTime:
dump_time(obj, out, 1);
break;
case UnixTime:
default:
dump_time(obj, out, 0);
break;
}
*out->cur++ = '}';
*out->cur = '\0';
} else {
if (oj_bigdecimal_class == clas) {
volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
if (Yes == out->opts->bigdec_as_num) {
dump_raw(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), out);
} else {
dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), 0, 0, out);
}
} else {
dump_nil(out);
}
}
}
static void
dump_obj_comp(VALUE obj, int depth, Out out) {
if (rb_respond_to(obj, oj_to_hash_id)) {
volatile 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, Qundef, depth, out->opts->mode, out);
} else if (rb_respond_to(obj, oj_as_json_id)) {
volatile VALUE aj = rb_funcall(obj, oj_as_json_id, 0);
// Catch the obvious brain damaged recursive dumping.
if (aj == obj) {
volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), 0, 0, out);
} else {
dump_val(aj, depth, out);
}
} else if (Yes == out->opts->to_json && rb_respond_to(obj, oj_to_json_id)) {
volatile VALUE rs;
const char *s;
int len;
rs = rb_funcall(obj, oj_to_json_id, 0);
s = rb_string_value_ptr((VALUE*)&rs);
len = (int)RSTRING_LEN(rs);
if (out->end - out->cur <= len + 1) {
grow(out, len);
}
memcpy(out->cur, s, len);
out->cur += len;
*out->cur = '\0';
} else {
VALUE clas = rb_obj_class(obj);
if (oj_bigdecimal_class == clas) {
volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
if (Yes == out->opts->bigdec_as_num) {
dump_raw(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), out);
} else {
dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), 0, 0, out);
}
#if (defined T_RATIONAL && defined RRATIONAL)
} else if (oj_datetime_class == clas || oj_date_class == clas || rb_cRational == clas) {
#else
} else if (oj_datetime_class == clas || oj_date_class == clas) {
#endif
volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), 0, 0, out);
} else {
dump_obj_attrs(obj, Qundef, 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) {
VALUE clas = rb_obj_class(obj);
if (oj_bigdecimal_class == clas) {
volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
dump_raw(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), out);
} else {
dump_obj_attrs(obj, clas, id, depth, out);
}
}
}
#ifdef RUBINIUS_RUBY
static int
isRbxHashAttr(const char *attr) {
const char *hashAttrs[] = {
"@capacity",
"@max_entries",
"@state",
"@mask",
"@size",
"@entries",
"@default_proc",
0 };
const char **ap;
for (ap = hashAttrs; 0 != *ap; ap++) {
if (0 == strcmp(attr, *ap)) {
return 1;
}
}
return 0;
}
#endif
#if HAS_IVAR_HELPERS
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 HAS_EXCEPTION_MAGIC
if (0 == strcmp("bt", attr) || 0 == strcmp("mesg", attr)) {
return ST_CONTINUE;
}
#endif
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;
}
#endif
static void
dump_obj_attrs(VALUE obj, VALUE clas, slot_t id, int depth, Out out) {
size_t size = 0;
int d2 = depth + 1;
int type = rb_type(obj);
if (out->end - out->cur <= 2) {
grow(out, 2);
}
*out->cur++ = '{';
if (Qundef != clas) {
const char *class_name = rb_class2name(clas);
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);
}
switch (type) {
case T_STRING:
size = d2 * out->indent + 14;
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
*out->cur++ = ',';
fill_indent(out, d2);
*out->cur++ = '"';
*out->cur++ = 's';
*out->cur++ = 'e';
*out->cur++ = 'l';
*out->cur++ = 'f';
*out->cur++ = '"';
*out->cur++ = ':';
dump_cstr(rb_string_value_ptr((VALUE*)&obj), RSTRING_LEN(obj), 0, 0, out);
break;
case T_ARRAY:
size = d2 * out->indent + 14;
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
*out->cur++ = ',';
fill_indent(out, d2);
*out->cur++ = '"';
*out->cur++ = 's';
*out->cur++ = 'e';
*out->cur++ = 'l';
*out->cur++ = 'f';
*out->cur++ = '"';
*out->cur++ = ':';
dump_array(obj, Qundef, depth + 1, out);
break;
case T_HASH:
size = d2 * out->indent + 14;
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
*out->cur++ = ',';
fill_indent(out, d2);
*out->cur++ = '"';
*out->cur++ = 's';
*out->cur++ = 'e';
*out->cur++ = 'l';
*out->cur++ = 'f';
*out->cur++ = '"';
*out->cur++ = ':';
dump_hash(obj, Qundef, depth + 1, out->opts->mode, out);
break;
default:
break;
}
{
int cnt;
#if HAS_IVAR_HELPERS
cnt = (int)rb_ivar_count(obj);
#else
volatile VALUE vars = rb_funcall2(obj, oj_instance_variables_id, 0, 0);
VALUE *np = RARRAY_PTR(vars);
ID vid;
const char *attr;
int i;
int first = 1;
cnt = (int)RARRAY_LEN(vars);
#endif
if (Qundef != clas && 0 < cnt) {
*out->cur++ = ',';
}
out->depth = depth + 1;
#if HAS_IVAR_HELPERS
rb_ivar_foreach(obj, dump_attr_cb, (VALUE)out);
if (',' == *(out->cur - 1)) {
out->cur--; // backup to overwrite last comma
}
#else
size = d2 * out->indent + 1;
for (i = cnt; 0 < i; i--, np++) {
vid = rb_to_id(*np);
attr = rb_id2name(vid);
#ifdef RUBINIUS_RUBY
if (T_HASH == type && isRbxHashAttr(attr)) {
continue;
}
#endif
if (first) {
first = 0;
} else {
*out->cur++ = ',';
}
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
fill_indent(out, d2);
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);
}
}
#endif
#if HAS_EXCEPTION_MAGIC
if (Qtrue == rb_obj_is_kind_of(obj, rb_eException)) {
volatile VALUE rv;
if (',' != *(out->cur - 1)) {
*out->cur++ = ',';
}
// message
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
fill_indent(out, d2);
dump_cstr("~mesg", 5, 0, 0, out);
*out->cur++ = ':';
rv = rb_funcall2(obj, rb_intern("message"), 0, 0);
dump_val(rv, d2, out);
if (out->end - out->cur <= 2) {
grow(out, 2);
}
*out->cur++ = ',';
// backtrace
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
fill_indent(out, d2);
dump_cstr("~bt", 3, 0, 0, out);
*out->cur++ = ':';
rv = rb_funcall2(obj, rb_intern("backtrace"), 0, 0);
dump_val(rv, d2, out);
if (out->end - out->cur <= 2) {
grow(out, 2);
}
}
#endif
out->depth = depth;
}
fill_indent(out, 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)) {
volatile 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, Qundef, depth, out->opts->mode, out);
} else if (rb_respond_to(obj, oj_as_json_id)) {
volatile VALUE aj = rb_funcall(obj, oj_as_json_id, 0);
// Catch the obvious brain damaged recursive dumping.
if (aj == obj) {
volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), 0, 0, out);
} else {
dump_val(aj, depth, out);
}
} else if (Yes == out->opts->to_json && rb_respond_to(obj, oj_to_json_id)) {
volatile VALUE rs = rb_funcall(obj, oj_to_json_id, 0);
const char *s;
int len;
s = rb_string_value_ptr((VALUE*)&rs);
len = (int)RSTRING_LEN(rs);
if (out->end - out->cur <= len) {
grow(out, len);
}
memcpy(out->cur, s, len);
out->cur += len;
} else {
volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0);
dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), 0, 0, 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);
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++ = '[';
if ('#' == *class_name) {
VALUE ma = rb_struct_s_members(clas);
const char *name;
int cnt = (int)RARRAY_LEN(ma);
*out->cur++ = '[';
for (i = 0; i < cnt; i++) {
name = rb_id2name(SYM2ID(rb_ary_entry(ma, i)));
len = strlen(name);
size = len + 3;
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
if (0 < i) {
*out->cur++ = ',';
}
*out->cur++ = '"';
memcpy(out->cur, name, len);
out->cur += len;
*out->cur++ = '"';
}
*out->cur++ = ']';
} else {
fill_indent(out, d3);
*out->cur++ = '"';
memcpy(out->cur, class_name, len);
out->cur += len;
*out->cur++ = '"';
}
*out->cur++ = ',';
size = d3 * out->indent + 2;
#ifdef RSTRUCT_LEN
{
VALUE *vp;
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++ = ',';
}
}
#else
{
// This is a bit risky as a struct in C ruby is not the same as a Struct
// class in interpreted Ruby so length() may not be defined.
int slen = FIX2INT(rb_funcall2(obj, oj_length_id, 0, 0));
for (i = 0; i < slen; i++) {
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
fill_indent(out, d3);
dump_val(rb_struct_aref(obj, INT2FIX(i)), d3, out);
*out->cur++ = ',';
}
}
#endif
out->cur--;
*out->cur++ = ']';
*out->cur++ = '}';
*out->cur = '\0';
}
static void
dump_odd(VALUE obj, Odd odd, VALUE clas, int depth, Out out) {
ID *idp;
AttrGetFunc *fp;
volatile VALUE v;
const char *name;
size_t size;
int d2 = depth + 1;
if (out->end - out->cur <= 2) {
grow(out, 2);
}
*out->cur++ = '{';
if (Qundef != clas) {
const char *class_name = rb_class2name(clas);
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);
*out->cur++ = ',';
}
size = d2 * out->indent + 1;
for (idp = odd->attrs, fp = odd->attrFuncs; 0 != *idp; idp++, fp++) {
size_t nlen;
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
name = rb_id2name(*idp);
nlen = strlen(name);
if (0 != *fp) {
v = (*fp)(obj);
} else if (0 == strchr(name, '.')) {
v = rb_funcall(obj, *idp, 0);
} else {
char nbuf[256];
char *n2 = nbuf;
char *n;
char *end;
ID i;
if (sizeof(nbuf) <= nlen) {
n2 = strdup(name);
} else {
strcpy(n2, name);
}
n = n2;
v = obj;
while (0 != (end = strchr(n, '.'))) {
*end = '\0';
i = rb_intern(n);
v = rb_funcall(v, i, 0);
n = end + 1;
}
i = rb_intern(n);
v = rb_funcall(v, i, 0);
if (nbuf != n2) {
free(n2);
}
}
fill_indent(out, d2);
dump_cstr(name, nlen, 0, 0, out);
*out->cur++ = ':';
dump_val(v, d2, out);
if (out->end - out->cur <= 2) {
grow(out, 2);
}
*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) {
int type = rb_type(obj);
if (MAX_DEPTH < depth) {
rb_raise(rb_eNoMemError, "Too deeply nested.\n");
}
switch (type) {
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_MODULE:
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_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_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;
default:
// Most developers have enough sense not to subclass primitive types but
// since these classes could potentially be subclassed a check for odd
// classes is performed.
{
VALUE clas = rb_obj_class(obj);
Odd odd;
if (ObjectMode == out->opts->mode && 0 != (odd = oj_get_odd(clas))) {
dump_odd(obj, odd, clas, depth + 1, out);
return;
}
switch (type) {
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, clas, depth, out); break;
}
break;
case T_ARRAY: dump_array(obj, clas, depth, out); break;
case T_HASH: dump_hash(obj, clas, depth, out->opts->mode, out); break;
#if (defined T_RATIONAL && defined RRATIONAL)
case T_RATIONAL:
#endif
case T_OBJECT:
switch (out->opts->mode) {
case StrictMode: dump_data_strict(obj, out); break;
case NullMode: dump_data_null(obj, 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: dump_data_strict(obj, out); break;
case NullMode: dump_data_null(obj, out); break;
case CompatMode: dump_data_comp(obj, depth, out);break;
case ObjectMode:
default: dump_data_obj(obj, depth, out); break;
}
break;
#if (defined T_COMPLEX && defined RCOMPLEX)
case T_COMPLEX:
#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;
}
}
}
}
void
oj_dump_obj_to_json(VALUE obj, Options copts, Out out) {
if (0 == out->buf) {
out->buf = ALLOC_N(char, 4096);
out->end = out->buf + 4095 - BUFFER_EXTRA; // 1 less than end plus extra for possible errors
out->allocated = 1;
}
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 (0 < out->indent) {
switch (*(out->cur - 1)) {
case ']':
case '}':
grow(out, 1);
*out->cur++ = '\n';
default:
break;
}
}
*out->cur = '\0';
if (Yes == copts->circular) {
oj_cache8_delete(out->circ_cache);
}
}
void
oj_write_obj_to_file(VALUE obj, const char *path, Options copts) {
char buf[4096];
struct _Out out;
size_t size;
FILE *f;
int ok;
out.buf = buf;
out.end = buf + sizeof(buf) - BUFFER_EXTRA;
out.allocated = 0;
oj_dump_obj_to_json(obj, copts, &out);
size = out.cur - out.buf;
if (0 == (f = fopen(path, "w"))) {
if (out.allocated) {
xfree(out.buf);
}
rb_raise(rb_eIOError, "%s\n", strerror(errno));
}
ok = (size == fwrite(out.buf, 1, size, f));
if (out.allocated) {
xfree(out.buf);
}
fclose(f);
if (!ok) {
int err = ferror(f);
rb_raise(rb_eIOError, "Write failed. [%d:%s]\n", err, strerror(err));
}
}
void
oj_write_obj_to_stream(VALUE obj, VALUE stream, Options copts) {
char buf[4096];
struct _Out out;
ssize_t size;
VALUE clas = rb_obj_class(stream);
#if !IS_WINDOWS
int fd;
VALUE s;
#endif
out.buf = buf;
out.end = buf + sizeof(buf) - BUFFER_EXTRA;
out.allocated = 0;
oj_dump_obj_to_json(obj, copts, &out);
size = out.cur - out.buf;
if (oj_stringio_class == clas) {
rb_funcall(stream, oj_write_id, 1, rb_str_new(out.buf, size));
#if !IS_WINDOWS
} else if (rb_respond_to(stream, oj_fileno_id) &&
Qnil != (s = rb_funcall(stream, oj_fileno_id, 0)) &&
0 != (fd = FIX2INT(s))) {
if (size != write(fd, out.buf, size)) {
if (out.allocated) {
xfree(out.buf);
}
rb_raise(rb_eIOError, "Write failed. [%d:%s]\n", errno, strerror(errno));
}
#endif
} else if (rb_respond_to(stream, oj_write_id)) {
rb_funcall(stream, oj_write_id, 1, rb_str_new(out.buf, size));
} else {
if (out.allocated) {
xfree(out.buf);
}
rb_raise(rb_eArgError, "to_stream() expected an IO Object.");
}
if (out.allocated) {
xfree(out.buf);
}
}
// dump leaf functions
inline static void
dump_chars(const char *s, size_t size, Out out) {
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
memcpy(out->cur, s, size);
out->cur += size;
*out->cur = '\0';
}
static void
dump_leaf_str(Leaf leaf, Out out) {
switch (leaf->value_type) {
case STR_VAL:
dump_cstr(leaf->str, strlen(leaf->str), 0, 0, out);
break;
case RUBY_VAL:
dump_cstr(rb_string_value_cstr(&leaf->value), RSTRING_LEN(leaf->value), 0, 0, out);
break;
case COL_VAL:
default:
rb_raise(rb_eTypeError, "Unexpected value type %02x.\n", leaf->value_type);
break;
}
}
static void
dump_leaf_fixnum(Leaf leaf, Out out) {
switch (leaf->value_type) {
case STR_VAL:
dump_chars(leaf->str, strlen(leaf->str), out);
break;
case RUBY_VAL:
if (T_BIGNUM == rb_type(leaf->value)) {
dump_bignum(leaf->value, out);
} else {
dump_fixnum(leaf->value, out);
}
break;
case COL_VAL:
default:
rb_raise(rb_eTypeError, "Unexpected value type %02x.\n", leaf->value_type);
break;
}
}
static void
dump_leaf_float(Leaf leaf, Out out) {
switch (leaf->value_type) {
case STR_VAL:
dump_chars(leaf->str, strlen(leaf->str), out);
break;
case RUBY_VAL:
dump_float(leaf->value, out);
break;
case COL_VAL:
default:
rb_raise(rb_eTypeError, "Unexpected value type %02x.\n", leaf->value_type);
break;
}
}
static void
dump_leaf_array(Leaf leaf, int depth, Out out) {
size_t size;
int d2 = depth + 1;
size = 2;
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
*out->cur++ = '[';
if (0 == leaf->elements) {
*out->cur++ = ']';
} else {
Leaf first = leaf->elements->next;
Leaf e = first;
size = d2 * out->indent + 2;
do {
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
fill_indent(out, d2);
dump_leaf(e, d2, out);
if (e->next != first) {
*out->cur++ = ',';
}
e = e->next;
} while (e != first);
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 void
dump_leaf_hash(Leaf leaf, int depth, Out out) {
size_t size;
int d2 = depth + 1;
size = 2;
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
*out->cur++ = '{';
if (0 == leaf->elements) {
*out->cur++ = '}';
} else {
Leaf first = leaf->elements->next;
Leaf e = first;
size = d2 * out->indent + 2;
do {
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
fill_indent(out, d2);
dump_cstr(e->key, strlen(e->key), 0, 0, out);
*out->cur++ = ':';
dump_leaf(e, d2, out);
if (e->next != first) {
*out->cur++ = ',';
}
e = e->next;
} while (e != first);
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 void
dump_leaf(Leaf leaf, int depth, Out out) {
switch (leaf->rtype) {
case T_NIL:
dump_nil(out);
break;
case T_TRUE:
dump_true(out);
break;
case T_FALSE:
dump_false(out);
break;
case T_STRING:
dump_leaf_str(leaf, out);
break;
case T_FIXNUM:
dump_leaf_fixnum(leaf, out);
break;
case T_FLOAT:
dump_leaf_float(leaf, out);
break;
case T_ARRAY:
dump_leaf_array(leaf, depth, out);
break;
case T_HASH:
dump_leaf_hash(leaf, depth, out);
break;
default:
rb_raise(rb_eTypeError, "Unexpected type %02x.\n", leaf->rtype);
break;
}
}
void
oj_dump_leaf_to_json(Leaf leaf, Options copts, Out out) {
if (0 == out->buf) {
out->buf = ALLOC_N(char, 4096);
out->end = out->buf + 4095 - BUFFER_EXTRA; // 1 less than end plus extra for possible errors
out->allocated = 1;
}
out->cur = out->buf;
out->circ_cnt = 0;
out->opts = copts;
out->hash_cnt = 0;
out->indent = copts->indent;
dump_leaf(leaf, 0, out);
}
void
oj_write_leaf_to_file(Leaf leaf, const char *path, Options copts) {
char buf[4096];
struct _Out out;
size_t size;
FILE *f;
out.buf = buf;
out.end = buf + sizeof(buf) - BUFFER_EXTRA;
out.allocated = 0;
oj_dump_leaf_to_json(leaf, 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));
}
if (out.allocated) {
xfree(out.buf);
}
fclose(f);
}
// string writer functions
static void
key_check(StrWriter sw, const char *key) {
DumpType type = sw->types[sw->depth];
if (0 == key && (ObjectNew == type || ObjectType == type)) {
rb_raise(rb_eStandardError, "Can not push onto an Object without a key.");
}
}
static void
push_type(StrWriter sw, DumpType type) {
if (sw->types_end <= sw->types + sw->depth + 1) {
size_t size = (sw->types_end - sw->types) * 2;
REALLOC_N(sw->types, char, size);
sw->types_end = sw->types + size;
}
sw->depth++;
sw->types[sw->depth] = type;
}
static void
maybe_comma(StrWriter sw) {
switch (sw->types[sw->depth]) {
case ObjectNew:
sw->types[sw->depth] = ObjectType;
break;
case ArrayNew:
sw->types[sw->depth] = ArrayType;
break;
case ObjectType:
case ArrayType:
// Always have a few characters available in the out.buf.
*sw->out.cur++ = ',';
break;
}
}
void
oj_str_writer_push_key(StrWriter sw, const char *key) {
DumpType type = sw->types[sw->depth];
long size;
if (sw->keyWritten) {
rb_raise(rb_eStandardError, "Can not push more than one key before pushing a non-key.");
}
if (ObjectNew != type && ObjectType != type) {
rb_raise(rb_eStandardError, "Can only push a key onto an Object.");
}
size = sw->depth * sw->out.indent + 3;
if (sw->out.end - sw->out.cur <= (long)size) {
grow(&sw->out, size);
}
maybe_comma(sw);
if (0 < sw->depth) {
fill_indent(&sw->out, sw->depth);
}
dump_cstr(key, strlen(key), 0, 0, &sw->out);
*sw->out.cur++ = ':';
sw->keyWritten = 1;
}
void
oj_str_writer_push_object(StrWriter sw, const char *key) {
if (sw->keyWritten) {
sw->keyWritten = 0;
if (sw->out.end - sw->out.cur <= 1) {
grow(&sw->out, 1);
}
} else {
long size;
key_check(sw, key);
size = sw->depth * sw->out.indent + 3;
if (sw->out.end - sw->out.cur <= (long)size) {
grow(&sw->out, size);
}
maybe_comma(sw);
if (0 < sw->depth) {
fill_indent(&sw->out, sw->depth);
}
if (0 != key) {
dump_cstr(key, strlen(key), 0, 0, &sw->out);
*sw->out.cur++ = ':';
}
}
*sw->out.cur++ = '{';
push_type(sw, ObjectNew);
}
void
oj_str_writer_push_array(StrWriter sw, const char *key) {
if (sw->keyWritten) {
sw->keyWritten = 0;
if (sw->out.end - sw->out.cur <= 1) {
grow(&sw->out, 1);
}
} else {
long size;
key_check(sw, key);
size = sw->depth * sw->out.indent + 3;
if (sw->out.end - sw->out.cur <= size) {
grow(&sw->out, size);
}
maybe_comma(sw);
if (0 < sw->depth) {
fill_indent(&sw->out, sw->depth);
}
if (0 != key) {
dump_cstr(key, strlen(key), 0, 0, &sw->out);
*sw->out.cur++ = ':';
}
}
*sw->out.cur++ = '[';
push_type(sw, ArrayNew);
}
void
oj_str_writer_push_value(StrWriter sw, VALUE val, const char *key) {
if (sw->keyWritten) {
sw->keyWritten = 0;
} else {
long size;
key_check(sw, key);
size = sw->depth * sw->out.indent + 3;
if (sw->out.end - sw->out.cur <= size) {
grow(&sw->out, size);
}
maybe_comma(sw);
if (0 < sw->depth) {
fill_indent(&sw->out, sw->depth);
}
if (0 != key) {
dump_cstr(key, strlen(key), 0, 0, &sw->out);
*sw->out.cur++ = ':';
}
}
dump_val(val, sw->depth, &sw->out);
}
void
oj_str_writer_push_json(StrWriter sw, const char *json, const char *key) {
if (sw->keyWritten) {
sw->keyWritten = 0;
} else {
long size;
key_check(sw, key);
size = sw->depth * sw->out.indent + 3;
if (sw->out.end - sw->out.cur <= size) {
grow(&sw->out, size);
}
maybe_comma(sw);
if (0 < sw->depth) {
fill_indent(&sw->out, sw->depth);
}
if (0 != key) {
dump_cstr(key, strlen(key), 0, 0, &sw->out);
*sw->out.cur++ = ':';
}
}
dump_raw(json, strlen(json), &sw->out);
}
void
oj_str_writer_pop(StrWriter sw) {
long size;
DumpType type = sw->types[sw->depth];
if (sw->keyWritten) {
sw->keyWritten = 0;
rb_raise(rb_eStandardError, "Can not pop after writing a key but no value.");
}
sw->depth--;
if (0 > sw->depth) {
rb_raise(rb_eStandardError, "Can not pop with no open array or object.");
}
size = sw->depth * sw->out.indent + 2;
if (sw->out.end - sw->out.cur <= size) {
grow(&sw->out, size);
}
fill_indent(&sw->out, sw->depth);
switch (type) {
case ObjectNew:
case ObjectType:
*sw->out.cur++ = '}';
break;
case ArrayNew:
case ArrayType:
*sw->out.cur++ = ']';
break;
}
if (0 == sw->depth && 0 <= sw->out.indent) {
*sw->out.cur++ = '\n';
}
}
void
oj_str_writer_pop_all(StrWriter sw) {
while (0 < sw->depth) {
oj_str_writer_pop(sw);
}
}
Jump to Line
Something went wrong with that request. Please try again.