summaryrefslogtreecommitdiff
path: root/ext/json
diff options
context:
space:
mode:
Diffstat (limited to 'ext/json')
-rw-r--r--ext/json/depend2
-rw-r--r--ext/json/ext/generator/extconf.rb10
-rw-r--r--ext/json/ext/generator/generator.c877
-rw-r--r--ext/json/ext/generator/unicode.c184
-rw-r--r--ext/json/ext/generator/unicode.h53
-rw-r--r--ext/json/ext/parser/extconf.rb10
-rw-r--r--ext/json/ext/parser/parser.c1752
-rw-r--r--ext/json/ext/parser/parser.rl639
-rw-r--r--ext/json/ext/parser/unicode.c156
-rw-r--r--ext/json/ext/parser/unicode.h58
-rw-r--r--ext/json/extconf.rb2
-rw-r--r--ext/json/fbuffer/fbuffer.h260
-rw-r--r--ext/json/generator/depend186
-rw-r--r--ext/json/generator/extconf.rb18
-rw-r--r--ext/json/generator/generator.c1997
-rw-r--r--ext/json/json.gemspec62
-rw-r--r--ext/json/json.h116
-rw-r--r--ext/json/lib/json.rb864
-rw-r--r--ext/json/lib/json/Array.xpm21
-rw-r--r--ext/json/lib/json/FalseClass.xpm21
-rw-r--r--ext/json/lib/json/Hash.xpm21
-rw-r--r--ext/json/lib/json/Key.xpm73
-rw-r--r--ext/json/lib/json/NilClass.xpm21
-rw-r--r--ext/json/lib/json/Numeric.xpm28
-rw-r--r--ext/json/lib/json/String.xpm96
-rw-r--r--ext/json/lib/json/TrueClass.xpm21
-rw-r--r--ext/json/lib/json/add/bigdecimal.rb58
-rw-r--r--ext/json/lib/json/add/complex.rb51
-rw-r--r--ext/json/lib/json/add/core.rb129
-rw-r--r--ext/json/lib/json/add/date.rb54
-rw-r--r--ext/json/lib/json/add/date_time.rb67
-rw-r--r--ext/json/lib/json/add/exception.rb49
-rw-r--r--ext/json/lib/json/add/ostruct.rb54
-rw-r--r--ext/json/lib/json/add/rails.rb58
-rw-r--r--ext/json/lib/json/add/range.rb54
-rw-r--r--ext/json/lib/json/add/rational.rb49
-rw-r--r--ext/json/lib/json/add/regexp.rb48
-rw-r--r--ext/json/lib/json/add/set.rb48
-rw-r--r--ext/json/lib/json/add/string.rb35
-rw-r--r--ext/json/lib/json/add/struct.rb52
-rw-r--r--ext/json/lib/json/add/symbol.rb52
-rw-r--r--ext/json/lib/json/add/time.rb52
-rw-r--r--ext/json/lib/json/common.rb1305
-rw-r--r--ext/json/lib/json/editor.rb1363
-rw-r--r--ext/json/lib/json/ext.rb40
-rw-r--r--ext/json/lib/json/ext/generator/state.rb103
-rw-r--r--ext/json/lib/json/generic_object.rb67
-rw-r--r--ext/json/lib/json/json.xpm1499
-rw-r--r--ext/json/lib/json/pure.rb75
-rw-r--r--ext/json/lib/json/pure/generator.rb394
-rw-r--r--ext/json/lib/json/pure/parser.rb260
-rw-r--r--ext/json/lib/json/version.rb10
-rw-r--r--ext/json/parser/depend182
-rw-r--r--ext/json/parser/extconf.rb20
-rw-r--r--ext/json/parser/parser.c1757
-rw-r--r--ext/json/simd/conf.rb24
-rw-r--r--ext/json/simd/simd.h208
-rw-r--r--ext/json/vendor/fpconv.c480
-rw-r--r--ext/json/vendor/jeaiii-ltoa.h267
-rw-r--r--ext/json/vendor/ryu.h819
60 files changed, 9056 insertions, 8275 deletions
diff --git a/ext/json/depend b/ext/json/depend
new file mode 100644
index 0000000000..0301ce074c
--- /dev/null
+++ b/ext/json/depend
@@ -0,0 +1,2 @@
+# AUTOGENERATED DEPENDENCIES START
+# AUTOGENERATED DEPENDENCIES END
diff --git a/ext/json/ext/generator/extconf.rb b/ext/json/ext/generator/extconf.rb
deleted file mode 100644
index 86ed7e6367..0000000000
--- a/ext/json/ext/generator/extconf.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-require 'mkmf'
-require 'rbconfig'
-
-if CONFIG['CC'] =~ /gcc/
- #$CFLAGS += ' -Wall -ggdb'
- $CFLAGS += ' -Wall'
-end
-
-have_header 'st.h'
-create_makefile 'json/ext/generator'
diff --git a/ext/json/ext/generator/generator.c b/ext/json/ext/generator/generator.c
deleted file mode 100644
index 97b06e983f..0000000000
--- a/ext/json/ext/generator/generator.c
+++ /dev/null
@@ -1,877 +0,0 @@
-/* vim: set cin et sw=4 ts=4: */
-
-#include "ruby.h"
-#include "ruby/st.h"
-#include "unicode.h"
-#include <string.h>
-#include <math.h>
-
-#define check_max_nesting(state, depth) do { \
- long current_nesting = 1 + depth; \
- if (state->max_nesting != 0 && current_nesting > state->max_nesting) \
- rb_raise(eNestingError, "nesting of %ld is too deep", current_nesting); \
-} while (0);
-
-static VALUE mJSON, mExt, mGenerator, cState, mGeneratorMethods, mObject,
- mHash, mArray, mInteger, mFloat, mString, mString_Extend,
- mTrueClass, mFalseClass, mNilClass, eGeneratorError,
- eCircularDatastructure, eNestingError;
-
-static ID i_to_s, i_to_json, i_new, i_indent, i_space, i_space_before,
- i_object_nl, i_array_nl, i_check_circular, i_max_nesting,
- i_allow_nan, i_pack, i_unpack, i_create_id, i_extend;
-
-typedef struct JSON_Generator_StateStruct {
- VALUE indent;
- VALUE space;
- VALUE space_before;
- VALUE object_nl;
- VALUE array_nl;
- int check_circular;
- VALUE seen;
- VALUE memo;
- VALUE depth;
- long max_nesting;
- int flag;
- int allow_nan;
-} JSON_Generator_State;
-
-#define GET_STATE(self) \
- JSON_Generator_State *state; \
- Data_Get_Struct(self, JSON_Generator_State, state);
-
-/*
- * Document-module: JSON::Ext::Generator
- *
- * This is the JSON generator implemented as a C extension. It can be
- * configured to be used by setting
- *
- * JSON.generator = JSON::Ext::Generator
- *
- * with the method generator= in JSON.
- *
- */
-
-static int hash_to_json_state_i(VALUE key, VALUE value, VALUE Vstate)
-{
- VALUE json, buf, Vdepth;
- GET_STATE(Vstate);
- buf = state->memo;
- Vdepth = state->depth;
-
- if (key == Qundef) return ST_CONTINUE;
- if (state->flag) {
- state->flag = 0;
- rb_str_buf_cat2(buf, ",");
- if (RSTRING_LEN(state->object_nl)) rb_str_buf_append(buf, state->object_nl);
- }
- if (RSTRING_LEN(state->object_nl)) {
- rb_str_buf_append(buf, rb_str_times(state->indent, Vdepth));
- }
- json = rb_funcall(rb_funcall(key, i_to_s, 0), i_to_json, 2, Vstate, Vdepth);
- Check_Type(json, T_STRING);
- rb_str_buf_append(buf, json);
- OBJ_INFECT(buf, json);
- if (RSTRING_LEN(state->space_before)) {
- rb_str_buf_append(buf, state->space_before);
- }
- rb_str_buf_cat2(buf, ":");
- if (RSTRING_LEN(state->space)) rb_str_buf_append(buf, state->space);
- json = rb_funcall(value, i_to_json, 2, Vstate, Vdepth);
- Check_Type(json, T_STRING);
- state->flag = 1;
- rb_str_buf_append(buf, json);
- OBJ_INFECT(buf, json);
- state->depth = Vdepth;
- state->memo = buf;
- return ST_CONTINUE;
-}
-
-inline static VALUE mHash_json_transfrom(VALUE self, VALUE Vstate, VALUE Vdepth) {
- long depth, len = RHASH_SIZE(self);
- VALUE result;
- GET_STATE(Vstate);
-
- depth = 1 + FIX2LONG(Vdepth);
- result = rb_str_buf_new(len);
- state->memo = result;
- state->depth = LONG2FIX(depth);
- state->flag = 0;
- rb_str_buf_cat2(result, "{");
- if (RSTRING_LEN(state->object_nl)) rb_str_buf_append(result, state->object_nl);
- rb_hash_foreach(self, hash_to_json_state_i, Vstate);
- if (RSTRING_LEN(state->object_nl)) rb_str_buf_append(result, state->object_nl);
- if (RSTRING_LEN(state->object_nl)) {
- rb_str_buf_append(result, rb_str_times(state->indent, Vdepth));
- }
- rb_str_buf_cat2(result, "}");
- return result;
-}
-
-static int hash_to_json_i(VALUE key, VALUE value, VALUE buf)
-{
- VALUE tmp;
-
- if (key == Qundef) return ST_CONTINUE;
- if (RSTRING_LEN(buf) > 1) rb_str_buf_cat2(buf, ",");
- tmp = rb_funcall(rb_funcall(key, i_to_s, 0), i_to_json, 0);
- Check_Type(tmp, T_STRING);
- rb_str_buf_append(buf, tmp);
- OBJ_INFECT(buf, tmp);
- rb_str_buf_cat2(buf, ":");
- tmp = rb_funcall(value, i_to_json, 0);
- Check_Type(tmp, T_STRING);
- rb_str_buf_append(buf, tmp);
- OBJ_INFECT(buf, tmp);
-
- return ST_CONTINUE;
-}
-
-/*
- * call-seq: to_json(state = nil, depth = 0)
- *
- * Returns a JSON string containing a JSON object, that is unparsed from
- * this Hash instance.
- * _state_ is a JSON::State object, that can also be used to configure the
- * produced JSON string output further.
- * _depth_ is used to find out nesting depth, to indent accordingly.
- */
-static VALUE mHash_to_json(int argc, VALUE *argv, VALUE self)
-{
- VALUE Vstate, Vdepth, result;
- long depth;
-
- rb_scan_args(argc, argv, "02", &Vstate, &Vdepth);
- depth = NIL_P(Vdepth) ? 0 : FIX2LONG(Vdepth);
- if (NIL_P(Vstate)) {
- long len = RHASH_SIZE(self);
- result = rb_str_buf_new(len);
- rb_str_buf_cat2(result, "{");
- rb_hash_foreach(self, hash_to_json_i, result);
- rb_str_buf_cat2(result, "}");
- } else {
- GET_STATE(Vstate);
- check_max_nesting(state, depth);
- if (state->check_circular) {
- VALUE self_id = rb_obj_id(self);
- if (RTEST(rb_hash_aref(state->seen, self_id))) {
- rb_raise(eCircularDatastructure,
- "circular data structures not supported!");
- }
- rb_hash_aset(state->seen, self_id, Qtrue);
- result = mHash_json_transfrom(self, Vstate, LONG2FIX(depth));
- rb_hash_delete(state->seen, self_id);
- } else {
- result = mHash_json_transfrom(self, Vstate, LONG2FIX(depth));
- }
- }
- OBJ_INFECT(result, self);
- return result;
-}
-
-inline static VALUE mArray_json_transfrom(VALUE self, VALUE Vstate, VALUE Vdepth) {
- long i, len = RARRAY_LEN(self);
- VALUE shift, result;
- long depth = NIL_P(Vdepth) ? 0 : FIX2LONG(Vdepth);
- VALUE delim = rb_str_new2(",");
- GET_STATE(Vstate);
-
- check_max_nesting(state, depth);
- if (state->check_circular) {
- VALUE self_id = rb_obj_id(self);
- rb_hash_aset(state->seen, self_id, Qtrue);
- result = rb_str_buf_new(len);
- if (RSTRING_LEN(state->array_nl)) rb_str_append(delim, state->array_nl);
- shift = rb_str_times(state->indent, LONG2FIX(depth + 1));
-
- rb_str_buf_cat2(result, "[");
- OBJ_INFECT(result, self);
- rb_str_buf_append(result, state->array_nl);
- for (i = 0; i < len; i++) {
- VALUE element = RARRAY_PTR(self)[i];
- if (RTEST(rb_hash_aref(state->seen, rb_obj_id(element)))) {
- rb_raise(eCircularDatastructure,
- "circular data structures not supported!");
- }
- OBJ_INFECT(result, element);
- if (i > 0) rb_str_buf_append(result, delim);
- rb_str_buf_append(result, shift);
- element = rb_funcall(element, i_to_json, 2, Vstate, LONG2FIX(depth + 1));
- Check_Type(element, T_STRING);
- rb_str_buf_append(result, element);
- }
- if (RSTRING_LEN(state->array_nl)) {
- rb_str_buf_append(result, state->array_nl);
- rb_str_buf_append(result, rb_str_times(state->indent, LONG2FIX(depth)));
- }
- rb_str_buf_cat2(result, "]");
- rb_hash_delete(state->seen, self_id);
- } else {
- result = rb_str_buf_new(len);
- OBJ_INFECT(result, self);
- if (RSTRING_LEN(state->array_nl)) rb_str_append(delim, state->array_nl);
- shift = rb_str_times(state->indent, LONG2FIX(depth + 1));
-
- rb_str_buf_cat2(result, "[");
- rb_str_buf_append(result, state->array_nl);
- for (i = 0; i < len; i++) {
- VALUE element = RARRAY_PTR(self)[i];
- OBJ_INFECT(result, element);
- if (i > 0) rb_str_buf_append(result, delim);
- rb_str_buf_append(result, shift);
- element = rb_funcall(element, i_to_json, 2, Vstate, LONG2FIX(depth + 1));
- Check_Type(element, T_STRING);
- rb_str_buf_append(result, element);
- }
- rb_str_buf_append(result, state->array_nl);
- if (RSTRING_LEN(state->array_nl)) {
- rb_str_buf_append(result, rb_str_times(state->indent, LONG2FIX(depth)));
- }
- rb_str_buf_cat2(result, "]");
- }
- return result;
-}
-
-/*
- * call-seq: to_json(state = nil, depth = 0)
- *
- * Returns a JSON string containing a JSON array, that is unparsed from
- * this Array instance.
- * _state_ is a JSON::State object, that can also be used to configure the
- * produced JSON string output further.
- * _depth_ is used to find out nesting depth, to indent accordingly.
- */
-static VALUE mArray_to_json(int argc, VALUE *argv, VALUE self) {
- VALUE Vstate, Vdepth, result;
-
- rb_scan_args(argc, argv, "02", &Vstate, &Vdepth);
- if (NIL_P(Vstate)) {
- long i, len = RARRAY_LEN(self);
- result = rb_str_buf_new(2 + 2 * len);
- rb_str_buf_cat2(result, "[");
- OBJ_INFECT(result, self);
- for (i = 0; i < len; i++) {
- VALUE element = RARRAY_PTR(self)[i];
- OBJ_INFECT(result, element);
- if (i > 0) rb_str_buf_cat2(result, ",");
- element = rb_funcall(element, i_to_json, 0);
- Check_Type(element, T_STRING);
- rb_str_buf_append(result, element);
- }
- rb_str_buf_cat2(result, "]");
- } else {
- result = mArray_json_transfrom(self, Vstate, Vdepth);
- }
- OBJ_INFECT(result, self);
- return result;
-}
-
-/*
- * call-seq: to_json(*)
- *
- * Returns a JSON string representation for this Integer number.
- */
-static VALUE mInteger_to_json(int argc, VALUE *argv, VALUE self)
-{
- return rb_funcall(self, i_to_s, 0);
-}
-
-/*
- * call-seq: to_json(*)
- *
- * Returns a JSON string representation for this Float number.
- */
-static VALUE mFloat_to_json(int argc, VALUE *argv, VALUE self)
-{
- JSON_Generator_State *state = NULL;
- VALUE Vstate, rest, tmp;
- double value = RFLOAT_VALUE(self);
- rb_scan_args(argc, argv, "01*", &Vstate, &rest);
- if (!NIL_P(Vstate)) Data_Get_Struct(Vstate, JSON_Generator_State, state);
- if (isinf(value)) {
- if (!state || state->allow_nan) {
- return rb_funcall(self, i_to_s, 0);
- } else {
- tmp = rb_funcall(self, i_to_s, 0);
- rb_raise(eGeneratorError, "%s not allowed in JSON", StringValueCStr(tmp));
- }
- } else if (isnan(value)) {
- if (!state || state->allow_nan) {
- return rb_funcall(self, i_to_s, 0);
- } else {
- tmp = rb_funcall(self, i_to_s, 0);
- rb_raise(eGeneratorError, "%s not allowed in JSON", StringValueCStr(tmp));
- }
- } else {
- return rb_funcall(self, i_to_s, 0);
- }
-}
-
-/*
- * call-seq: String.included(modul)
- *
- * Extends _modul_ with the String::Extend module.
- */
-static VALUE mString_included_s(VALUE self, VALUE modul) {
- return rb_funcall(modul, i_extend, 1, mString_Extend);
-}
-
-/*
- * call-seq: to_json(*)
- *
- * This string should be encoded with UTF-8 A call to this method
- * returns a JSON string encoded with UTF16 big endian characters as
- * \u????.
- */
-static VALUE mString_to_json(int argc, VALUE *argv, VALUE self)
-{
- VALUE result = rb_str_buf_new(RSTRING_LEN(self));
- rb_str_buf_cat2(result, "\"");
- JSON_convert_UTF8_to_JSON(result, self, strictConversion);
- rb_str_buf_cat2(result, "\"");
- return result;
-}
-
-/*
- * call-seq: to_json_raw_object()
- *
- * This method creates a raw object hash, that can be nested into
- * other data structures and will be unparsed as a raw string. This
- * method should be used, if you want to convert raw strings to JSON
- * instead of UTF-8 strings, e. g. binary data.
- */
-static VALUE mString_to_json_raw_object(VALUE self) {
- VALUE ary;
- VALUE result = rb_hash_new();
- rb_hash_aset(result, rb_funcall(mJSON, i_create_id, 0), rb_class_name(rb_obj_class(self)));
- ary = rb_funcall(self, i_unpack, 1, rb_str_new2("C*"));
- rb_hash_aset(result, rb_str_new2("raw"), ary);
- return result;
-}
-
-/*
- * call-seq: to_json_raw(*args)
- *
- * This method creates a JSON text from the result of a call to
- * to_json_raw_object of this String.
- */
-static VALUE mString_to_json_raw(int argc, VALUE *argv, VALUE self) {
- VALUE obj = mString_to_json_raw_object(self);
- Check_Type(obj, T_HASH);
- return mHash_to_json(argc, argv, obj);
-}
-
-/*
- * call-seq: json_create(o)
- *
- * Raw Strings are JSON Objects (the raw bytes are stored in an array for the
- * key "raw"). The Ruby String can be created by this module method.
- */
-static VALUE mString_Extend_json_create(VALUE self, VALUE o) {
- VALUE ary;
- Check_Type(o, T_HASH);
- ary = rb_hash_aref(o, rb_str_new2("raw"));
- return rb_funcall(ary, i_pack, 1, rb_str_new2("C*"));
-}
-
-/*
- * call-seq: to_json(state = nil, depth = 0)
- *
- * Returns a JSON string for true: 'true'.
- */
-static VALUE mTrueClass_to_json(int argc, VALUE *argv, VALUE self)
-{
- return rb_str_new2("true");
-}
-
-/*
- * call-seq: to_json(state = nil, depth = 0)
- *
- * Returns a JSON string for false: 'false'.
- */
-static VALUE mFalseClass_to_json(int argc, VALUE *argv, VALUE self)
-{
- return rb_str_new2("false");
-}
-
-/*
- * call-seq: to_json(state = nil, depth = 0)
- *
- */
-static VALUE mNilClass_to_json(int argc, VALUE *argv, VALUE self)
-{
- return rb_str_new2("null");
-}
-
-/*
- * call-seq: to_json(*)
- *
- * Converts this object to a string (calling #to_s), converts
- * it to a JSON string, and returns the result. This is a fallback, if no
- * special method #to_json was defined for some object.
- */
-static VALUE mObject_to_json(int argc, VALUE *argv, VALUE self)
-{
- VALUE string = rb_funcall(self, i_to_s, 0);
- Check_Type(string, T_STRING);
- return mString_to_json(argc, argv, string);
-}
-
-/*
- * Document-class: JSON::Ext::Generator::State
- *
- * This class is used to create State instances, that are use to hold data
- * while generating a JSON text from a a Ruby data structure.
- */
-
-static void State_mark(JSON_Generator_State *state)
-{
- rb_gc_mark_maybe(state->indent);
- rb_gc_mark_maybe(state->space);
- rb_gc_mark_maybe(state->space_before);
- rb_gc_mark_maybe(state->object_nl);
- rb_gc_mark_maybe(state->array_nl);
- rb_gc_mark_maybe(state->seen);
- rb_gc_mark_maybe(state->memo);
- rb_gc_mark_maybe(state->depth);
-}
-
-static JSON_Generator_State *State_allocate()
-{
- JSON_Generator_State *state = ALLOC(JSON_Generator_State);
- return state;
-}
-
-static VALUE cState_s_allocate(VALUE klass)
-{
- JSON_Generator_State *state = State_allocate();
- return Data_Wrap_Struct(klass, State_mark, -1, state);
-}
-
-/*
- * call-seq: configure(opts)
- *
- * Configure this State instance with the Hash _opts_, and return
- * itself.
- */
-static inline VALUE cState_configure(VALUE self, VALUE opts)
-{
- VALUE tmp;
- GET_STATE(self);
- tmp = rb_convert_type(opts, T_HASH, "Hash", "to_hash");
- if (NIL_P(tmp)) tmp = rb_convert_type(opts, T_HASH, "Hash", "to_h");
- if (NIL_P(tmp)) {
- rb_raise(rb_eArgError, "opts has to be hash like or convertable into a hash");
- }
- opts = tmp;
- tmp = rb_hash_aref(opts, ID2SYM(i_indent));
- if (RTEST(tmp)) {
- Check_Type(tmp, T_STRING);
- state->indent = tmp;
- }
- tmp = rb_hash_aref(opts, ID2SYM(i_space));
- if (RTEST(tmp)) {
- Check_Type(tmp, T_STRING);
- state->space = tmp;
- }
- tmp = rb_hash_aref(opts, ID2SYM(i_space_before));
- if (RTEST(tmp)) {
- Check_Type(tmp, T_STRING);
- state->space_before = tmp;
- }
- tmp = rb_hash_aref(opts, ID2SYM(i_array_nl));
- if (RTEST(tmp)) {
- Check_Type(tmp, T_STRING);
- state->array_nl = tmp;
- }
- tmp = rb_hash_aref(opts, ID2SYM(i_object_nl));
- if (RTEST(tmp)) {
- Check_Type(tmp, T_STRING);
- state->object_nl = tmp;
- }
- tmp = ID2SYM(i_check_circular);
- if (st_lookup(RHASH_TBL(opts), tmp, 0)) {
- tmp = rb_hash_aref(opts, ID2SYM(i_check_circular));
- state->check_circular = RTEST(tmp);
- } else {
- state->check_circular = 1;
- }
- tmp = ID2SYM(i_max_nesting);
- state->max_nesting = 19;
- if (st_lookup(RHASH_TBL(opts), tmp, 0)) {
- VALUE max_nesting = rb_hash_aref(opts, tmp);
- if (RTEST(max_nesting)) {
- Check_Type(max_nesting, T_FIXNUM);
- state->max_nesting = FIX2LONG(max_nesting);
- } else {
- state->max_nesting = 0;
- }
- }
- tmp = rb_hash_aref(opts, ID2SYM(i_allow_nan));
- state->allow_nan = RTEST(tmp);
- return self;
-}
-
-/*
- * call-seq: to_h
- *
- * Returns the configuration instance variables as a hash, that can be
- * passed to the configure method.
- */
-static VALUE cState_to_h(VALUE self)
-{
- VALUE result = rb_hash_new();
- GET_STATE(self);
- rb_hash_aset(result, ID2SYM(i_indent), state->indent);
- rb_hash_aset(result, ID2SYM(i_space), state->space);
- rb_hash_aset(result, ID2SYM(i_space_before), state->space_before);
- rb_hash_aset(result, ID2SYM(i_object_nl), state->object_nl);
- rb_hash_aset(result, ID2SYM(i_array_nl), state->array_nl);
- rb_hash_aset(result, ID2SYM(i_check_circular), state->check_circular ? Qtrue : Qfalse);
- rb_hash_aset(result, ID2SYM(i_allow_nan), state->allow_nan ? Qtrue : Qfalse);
- rb_hash_aset(result, ID2SYM(i_max_nesting), LONG2FIX(state->max_nesting));
- return result;
-}
-
-
-/*
- * call-seq: new(opts = {})
- *
- * Instantiates a new State object, configured by _opts_.
- *
- * _opts_ can have the following keys:
- *
- * * *indent*: a string used to indent levels (default: ''),
- * * *space*: a string that is put after, a : or , delimiter (default: ''),
- * * *space_before*: a string that is put before a : pair delimiter (default: ''),
- * * *object_nl*: a string that is put at the end of a JSON object (default: ''),
- * * *array_nl*: a string that is put at the end of a JSON array (default: ''),
- * * *check_circular*: true if checking for circular data structures
- * should be done, false (the default) otherwise.
- * * *allow_nan*: true if NaN, Infinity, and -Infinity should be
- * generated, otherwise an exception is thrown, if these values are
- * encountered. This options defaults to false.
- */
-static VALUE cState_initialize(int argc, VALUE *argv, VALUE self)
-{
- VALUE opts;
- GET_STATE(self);
-
- rb_scan_args(argc, argv, "01", &opts);
- state->indent = rb_str_new2("");
- state->space = rb_str_new2("");
- state->space_before = rb_str_new2("");
- state->array_nl = rb_str_new2("");
- state->object_nl = rb_str_new2("");
- if (NIL_P(opts)) {
- state->check_circular = 1;
- state->allow_nan = 0;
- state->max_nesting = 19;
- } else {
- cState_configure(self, opts);
- }
- state->seen = rb_hash_new();
- state->memo = Qnil;
- state->depth = INT2FIX(0);
- return self;
-}
-
-/*
- * call-seq: from_state(opts)
- *
- * Creates a State object from _opts_, which ought to be Hash to create a
- * new State instance configured by _opts_, something else to create an
- * unconfigured instance. If _opts_ is a State object, it is just returned.
- */
-static VALUE cState_from_state_s(VALUE self, VALUE opts)
-{
- if (rb_obj_is_kind_of(opts, self)) {
- return opts;
- } else if (rb_obj_is_kind_of(opts, rb_cHash)) {
- return rb_funcall(self, i_new, 1, opts);
- } else {
- return rb_funcall(self, i_new, 0);
- }
-}
-
-/*
- * call-seq: indent()
- *
- * This string is used to indent levels in the JSON text.
- */
-static VALUE cState_indent(VALUE self)
-{
- GET_STATE(self);
- return state->indent;
-}
-
-/*
- * call-seq: indent=(indent)
- *
- * This string is used to indent levels in the JSON text.
- */
-static VALUE cState_indent_set(VALUE self, VALUE indent)
-{
- GET_STATE(self);
- Check_Type(indent, T_STRING);
- return state->indent = indent;
-}
-
-/*
- * call-seq: space()
- *
- * This string is used to insert a space between the tokens in a JSON
- * string.
- */
-static VALUE cState_space(VALUE self)
-{
- GET_STATE(self);
- return state->space;
-}
-
-/*
- * call-seq: space=(space)
- *
- * This string is used to insert a space between the tokens in a JSON
- * string.
- */
-static VALUE cState_space_set(VALUE self, VALUE space)
-{
- GET_STATE(self);
- Check_Type(space, T_STRING);
- return state->space = space;
-}
-
-/*
- * call-seq: space_before()
- *
- * This string is used to insert a space before the ':' in JSON objects.
- */
-static VALUE cState_space_before(VALUE self)
-{
- GET_STATE(self);
- return state->space_before;
-}
-
-/*
- * call-seq: space_before=(space_before)
- *
- * This string is used to insert a space before the ':' in JSON objects.
- */
-static VALUE cState_space_before_set(VALUE self, VALUE space_before)
-{
- GET_STATE(self);
- Check_Type(space_before, T_STRING);
- return state->space_before = space_before;
-}
-
-/*
- * call-seq: object_nl()
- *
- * This string is put at the end of a line that holds a JSON object (or
- * Hash).
- */
-static VALUE cState_object_nl(VALUE self)
-{
- GET_STATE(self);
- return state->object_nl;
-}
-
-/*
- * call-seq: object_nl=(object_nl)
- *
- * This string is put at the end of a line that holds a JSON object (or
- * Hash).
- */
-static VALUE cState_object_nl_set(VALUE self, VALUE object_nl)
-{
- GET_STATE(self);
- Check_Type(object_nl, T_STRING);
- return state->object_nl = object_nl;
-}
-
-/*
- * call-seq: array_nl()
- *
- * This string is put at the end of a line that holds a JSON array.
- */
-static VALUE cState_array_nl(VALUE self)
-{
- GET_STATE(self);
- return state->array_nl;
-}
-
-/*
- * call-seq: array_nl=(array_nl)
- *
- * This string is put at the end of a line that holds a JSON array.
- */
-static VALUE cState_array_nl_set(VALUE self, VALUE array_nl)
-{
- GET_STATE(self);
- Check_Type(array_nl, T_STRING);
- return state->array_nl = array_nl;
-}
-
-/*
- * call-seq: check_circular?
- *
- * Returns true, if circular data structures should be checked,
- * otherwise returns false.
- */
-static VALUE cState_check_circular_p(VALUE self)
-{
- GET_STATE(self);
- return state->check_circular ? Qtrue : Qfalse;
-}
-
-/*
- * call-seq: max_nesting
- *
- * This integer returns the maximum level of data structure nesting in
- * the generated JSON, max_nesting = 0 if no maximum is checked.
- */
-static VALUE cState_max_nesting(VALUE self)
-{
- GET_STATE(self);
- return LONG2FIX(state->max_nesting);
-}
-
-/*
- * call-seq: max_nesting=(depth)
- *
- * This sets the maximum level of data structure nesting in the generated JSON
- * to the integer depth, max_nesting = 0 if no maximum should be checked.
- */
-static VALUE cState_max_nesting_set(VALUE self, VALUE depth)
-{
- GET_STATE(self);
- Check_Type(depth, T_FIXNUM);
- state->max_nesting = FIX2LONG(depth);
- return Qnil;
-}
-
-/*
- * call-seq: allow_nan?
- *
- * Returns true, if NaN, Infinity, and -Infinity should be generated, otherwise
- * returns false.
- */
-static VALUE cState_allow_nan_p(VALUE self)
-{
- GET_STATE(self);
- return state->allow_nan ? Qtrue : Qfalse;
-}
-
-/*
- * call-seq: seen?(object)
- *
- * Returns _true_, if _object_ was already seen during this generating run.
- */
-static VALUE cState_seen_p(VALUE self, VALUE object)
-{
- GET_STATE(self);
- return rb_hash_aref(state->seen, rb_obj_id(object));
-}
-
-/*
- * call-seq: remember(object)
- *
- * Remember _object_, to find out if it was already encountered (if a cyclic
- * data structure is rendered).
- */
-static VALUE cState_remember(VALUE self, VALUE object)
-{
- GET_STATE(self);
- return rb_hash_aset(state->seen, rb_obj_id(object), Qtrue);
-}
-
-/*
- * call-seq: forget(object)
- *
- * Forget _object_ for this generating run.
- */
-static VALUE cState_forget(VALUE self, VALUE object)
-{
- GET_STATE(self);
- return rb_hash_delete(state->seen, rb_obj_id(object));
-}
-
-/*
- *
- */
-void Init_generator()
-{
- rb_require("json/common");
- mJSON = rb_define_module("JSON");
- mExt = rb_define_module_under(mJSON, "Ext");
- mGenerator = rb_define_module_under(mExt, "Generator");
- eGeneratorError = rb_path2class("JSON::GeneratorError");
- eCircularDatastructure = rb_path2class("JSON::CircularDatastructure");
- eNestingError = rb_path2class("JSON::NestingError");
- cState = rb_define_class_under(mGenerator, "State", rb_cObject);
- rb_define_alloc_func(cState, cState_s_allocate);
- rb_define_singleton_method(cState, "from_state", cState_from_state_s, 1);
- rb_define_method(cState, "initialize", cState_initialize, -1);
-
- rb_define_method(cState, "indent", cState_indent, 0);
- rb_define_method(cState, "indent=", cState_indent_set, 1);
- rb_define_method(cState, "space", cState_space, 0);
- rb_define_method(cState, "space=", cState_space_set, 1);
- rb_define_method(cState, "space_before", cState_space_before, 0);
- rb_define_method(cState, "space_before=", cState_space_before_set, 1);
- rb_define_method(cState, "object_nl", cState_object_nl, 0);
- rb_define_method(cState, "object_nl=", cState_object_nl_set, 1);
- rb_define_method(cState, "array_nl", cState_array_nl, 0);
- rb_define_method(cState, "array_nl=", cState_array_nl_set, 1);
- rb_define_method(cState, "check_circular?", cState_check_circular_p, 0);
- rb_define_method(cState, "max_nesting", cState_max_nesting, 0);
- rb_define_method(cState, "max_nesting=", cState_max_nesting_set, 1);
- rb_define_method(cState, "allow_nan?", cState_allow_nan_p, 0);
- rb_define_method(cState, "seen?", cState_seen_p, 1);
- rb_define_method(cState, "remember", cState_remember, 1);
- rb_define_method(cState, "forget", cState_forget, 1);
- rb_define_method(cState, "configure", cState_configure, 1);
- rb_define_method(cState, "to_h", cState_to_h, 0);
-
- mGeneratorMethods = rb_define_module_under(mGenerator, "GeneratorMethods");
- mObject = rb_define_module_under(mGeneratorMethods, "Object");
- rb_define_method(mObject, "to_json", mObject_to_json, -1);
- mHash = rb_define_module_under(mGeneratorMethods, "Hash");
- rb_define_method(mHash, "to_json", mHash_to_json, -1);
- mArray = rb_define_module_under(mGeneratorMethods, "Array");
- rb_define_method(mArray, "to_json", mArray_to_json, -1);
- mInteger = rb_define_module_under(mGeneratorMethods, "Integer");
- rb_define_method(mInteger, "to_json", mInteger_to_json, -1);
- mFloat = rb_define_module_under(mGeneratorMethods, "Float");
- rb_define_method(mFloat, "to_json", mFloat_to_json, -1);
- mString = rb_define_module_under(mGeneratorMethods, "String");
- rb_define_singleton_method(mString, "included", mString_included_s, 1);
- rb_define_method(mString, "to_json", mString_to_json, -1);
- rb_define_method(mString, "to_json_raw", mString_to_json_raw, -1);
- rb_define_method(mString, "to_json_raw_object", mString_to_json_raw_object, 0);
- mString_Extend = rb_define_module_under(mString, "Extend");
- rb_define_method(mString_Extend, "json_create", mString_Extend_json_create, 1);
- mTrueClass = rb_define_module_under(mGeneratorMethods, "TrueClass");
- rb_define_method(mTrueClass, "to_json", mTrueClass_to_json, -1);
- mFalseClass = rb_define_module_under(mGeneratorMethods, "FalseClass");
- rb_define_method(mFalseClass, "to_json", mFalseClass_to_json, -1);
- mNilClass = rb_define_module_under(mGeneratorMethods, "NilClass");
- rb_define_method(mNilClass, "to_json", mNilClass_to_json, -1);
-
- i_to_s = rb_intern("to_s");
- i_to_json = rb_intern("to_json");
- i_new = rb_intern("new");
- i_indent = rb_intern("indent");
- i_space = rb_intern("space");
- i_space_before = rb_intern("space_before");
- i_object_nl = rb_intern("object_nl");
- i_array_nl = rb_intern("array_nl");
- i_check_circular = rb_intern("check_circular");
- i_max_nesting = rb_intern("max_nesting");
- i_allow_nan = rb_intern("allow_nan");
- i_pack = rb_intern("pack");
- i_unpack = rb_intern("unpack");
- i_create_id = rb_intern("create_id");
- i_extend = rb_intern("extend");
-}
diff --git a/ext/json/ext/generator/unicode.c b/ext/json/ext/generator/unicode.c
deleted file mode 100644
index 44e1f41f98..0000000000
--- a/ext/json/ext/generator/unicode.c
+++ /dev/null
@@ -1,184 +0,0 @@
-/* vim: set cin et sw=4 ts=4: */
-
-#include "unicode.h"
-
-#define unicode_escape(buffer, character) \
- snprintf(buf, 7, "\\u%04x", (unsigned int) (character)); \
- rb_str_buf_cat(buffer, buf, 6);
-
-/*
- * Copyright 2001-2004 Unicode, Inc.
- *
- * Disclaimer
- *
- * This source code is provided as is by Unicode, Inc. No claims are
- * made as to fitness for any particular purpose. No warranties of any
- * kind are expressed or implied. The recipient agrees to determine
- * applicability of information provided. If this file has been
- * purchased on magnetic or optical media from Unicode, Inc., the
- * sole remedy for any claim will be exchange of defective media
- * within 90 days of receipt.
- *
- * Limitations on Rights to Redistribute This Code
- *
- * Unicode, Inc. hereby grants the right to freely use the information
- * supplied in this file in the creation of products supporting the
- * Unicode Standard, and to make copies of this file in any form
- * for internal or external distribution as long as this notice
- * remains attached.
- */
-
-/*
- * Index into the table below with the first byte of a UTF-8 sequence to
- * get the number of trailing bytes that are supposed to follow it.
- * Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is
- * left as-is for anyone who may want to do such conversion, which was
- * allowed in earlier algorithms.
- */
-static const char trailingBytesForUTF8[256] = {
- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
- 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
- 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5
-};
-
-/*
- * Magic values subtracted from a buffer value during UTF8 conversion.
- * This table contains as many values as there might be trailing bytes
- * in a UTF-8 sequence.
- */
-static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL,
- 0x03C82080UL, 0xFA082080UL, 0x82082080UL };
-
-/*
- * Once the bits are split out into bytes of UTF-8, this is a mask OR-ed
- * into the first byte, depending on how many bytes follow. There are
- * as many entries in this table as there are UTF-8 sequence types.
- * (I.e., one byte sequence, two byte... etc.). Remember that sequencs
- * for *legal* UTF-8 will be 4 or fewer bytes total.
- */
-static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
-
-/*
- * Utility routine to tell whether a sequence of bytes is legal UTF-8.
- * This must be called with the length pre-determined by the first byte.
- * If not calling this from ConvertUTF8to*, then the length can be set by:
- * length = trailingBytesForUTF8[*source]+1;
- * and the sequence is illegal right away if there aren't that many bytes
- * available.
- * If presented with a length > 4, this returns 0. The Unicode
- * definition of UTF-8 goes up to 4-byte sequences.
- */
-
-inline static unsigned char isLegalUTF8(const UTF8 *source, int length)
-{
- UTF8 a;
- const UTF8 *srcptr = source+length;
- switch (length) {
- default: return 0;
- /* Everything else falls through when "1"... */
- case 4: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return 0;
- case 3: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return 0;
- case 2: if ((a = (*--srcptr)) > 0xBF) return 0;
-
- switch (*source) {
- /* no fall-through in this inner switch */
- case 0xE0: if (a < 0xA0) return 0; break;
- case 0xED: if (a > 0x9F) return 0; break;
- case 0xF0: if (a < 0x90) return 0; break;
- case 0xF4: if (a > 0x8F) return 0; break;
- default: if (a < 0x80) return 0;
- }
-
- case 1: if (*source >= 0x80 && *source < 0xC2) return 0;
- }
- if (*source > 0xF4) return 0;
- return 1;
-}
-
-void JSON_convert_UTF8_to_JSON(VALUE buffer, VALUE string, ConversionFlags flags)
-{
- char buf[7];
- const UTF8* source = (UTF8 *) RSTRING_PTR(string);
- const UTF8* sourceEnd = source + RSTRING_LEN(string);
-
- while (source < sourceEnd) {
- UTF32 ch = 0;
- unsigned short extraBytesToRead = trailingBytesForUTF8[*source];
- if (source + extraBytesToRead >= sourceEnd) {
- rb_raise(rb_path2class("JSON::GeneratorError"),
- "partial character in source, but hit end");
- }
- if (!isLegalUTF8(source, extraBytesToRead+1)) {
- rb_raise(rb_path2class("JSON::GeneratorError"),
- "source sequence is illegal/malformed");
- }
- /*
- * The cases all fall through. See "Note A" below.
- */
- switch (extraBytesToRead) {
- case 5: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */
- case 4: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */
- case 3: ch += *source++; ch <<= 6;
- case 2: ch += *source++; ch <<= 6;
- case 1: ch += *source++; ch <<= 6;
- case 0: ch += *source++;
- }
- ch -= offsetsFromUTF8[extraBytesToRead];
-
- if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */
- /* UTF-16 surrogate values are illegal in UTF-32 */
- if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) {
- if (flags == strictConversion) {
- source -= (extraBytesToRead+1); /* return to the illegal value itself */
- rb_raise(rb_path2class("JSON::GeneratorError"),
- "source sequence is illegal/malformed");
- } else {
- unicode_escape(buffer, UNI_REPLACEMENT_CHAR);
- }
- } else {
- /* normal case */
- if (ch == '"') {
- rb_str_buf_cat2(buffer, "\\\"");
- } else if (ch == '\\') {
- rb_str_buf_cat2(buffer, "\\\\");
- } else if (ch == '/') {
- rb_str_buf_cat2(buffer, "\\/");
- } else if (ch >= 0x20 && ch <= 0x7f) {
- rb_str_buf_cat(buffer, (char *) source - 1, 1);
- } else if (ch == '\n') {
- rb_str_buf_cat2(buffer, "\\n");
- } else if (ch == '\r') {
- rb_str_buf_cat2(buffer, "\\r");
- } else if (ch == '\t') {
- rb_str_buf_cat2(buffer, "\\t");
- } else if (ch == '\f') {
- rb_str_buf_cat2(buffer, "\\f");
- } else if (ch == '\b') {
- rb_str_buf_cat2(buffer, "\\b");
- } else if (ch < 0x20) {
- unicode_escape(buffer, (UTF16) ch);
- } else {
- unicode_escape(buffer, (UTF16) ch);
- }
- }
- } else if (ch > UNI_MAX_UTF16) {
- if (flags == strictConversion) {
- source -= (extraBytesToRead+1); /* return to the start */
- rb_raise(rb_path2class("JSON::GeneratorError"),
- "source sequence is illegal/malformed");
- } else {
- unicode_escape(buffer, UNI_REPLACEMENT_CHAR);
- }
- } else {
- /* target is a character in range 0xFFFF - 0x10FFFF. */
- ch -= halfBase;
- unicode_escape(buffer, (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START));
- unicode_escape(buffer, (UTF16)((ch & halfMask) + UNI_SUR_LOW_START));
- }
- }
-}
diff --git a/ext/json/ext/generator/unicode.h b/ext/json/ext/generator/unicode.h
deleted file mode 100644
index 841474bcea..0000000000
--- a/ext/json/ext/generator/unicode.h
+++ /dev/null
@@ -1,53 +0,0 @@
-#include "ruby.h"
-
-#ifndef _GENERATOR_UNICODE_H_
-#define _GENERATOR_UNICODE_H_
-
-typedef enum {
- conversionOK = 0, /* conversion successful */
- sourceExhausted, /* partial character in source, but hit end */
- targetExhausted, /* insuff. room in target for conversion */
- sourceIllegal /* source sequence is illegal/malformed */
-} ConversionResult;
-
-typedef enum {
- strictConversion = 0,
- lenientConversion
-} ConversionFlags;
-
-typedef unsigned long UTF32; /* at least 32 bits */
-typedef unsigned short UTF16; /* at least 16 bits */
-typedef unsigned char UTF8; /* typically 8 bits */
-
-#define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD
-#define UNI_MAX_BMP (UTF32)0x0000FFFF
-#define UNI_MAX_UTF16 (UTF32)0x0010FFFF
-#define UNI_MAX_UTF32 (UTF32)0x7FFFFFFF
-#define UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF
-
-#define UNI_SUR_HIGH_START (UTF32)0xD800
-#define UNI_SUR_HIGH_END (UTF32)0xDBFF
-#define UNI_SUR_LOW_START (UTF32)0xDC00
-#define UNI_SUR_LOW_END (UTF32)0xDFFF
-
-static const int halfShift = 10; /* used for shifting by 10 bits */
-
-static const UTF32 halfBase = 0x0010000UL;
-static const UTF32 halfMask = 0x3FFUL;
-
-void JSON_convert_UTF8_to_JSON(VALUE buffer, VALUE string, ConversionFlags flags);
-
-#ifndef RARRAY_PTR
-#define RARRAY_PTR(ARRAY) RARRAY(ARRAY)->ptr
-#endif
-#ifndef RARRAY_LEN
-#define RARRAY_LEN(ARRAY) RARRAY(ARRAY)->len
-#endif
-#ifndef RSTRING_PTR
-#define RSTRING_PTR(string) RSTRING(string)->ptr
-#endif
-#ifndef RSTRING_LEN
-#define RSTRING_LEN(string) RSTRING(string)->len
-#endif
-
-#endif
diff --git a/ext/json/ext/parser/extconf.rb b/ext/json/ext/parser/extconf.rb
deleted file mode 100644
index b2e7b051a4..0000000000
--- a/ext/json/ext/parser/extconf.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-require 'mkmf'
-require 'rbconfig'
-
-if CONFIG['CC'] =~ /gcc/
- #$CFLAGS += ' -Wall -ggdb'
- $CFLAGS += ' -Wall'
-end
-
-have_header 'st.h'
-create_makefile 'json/ext/parser'
diff --git a/ext/json/ext/parser/parser.c b/ext/json/ext/parser/parser.c
deleted file mode 100644
index 25a8ae1926..0000000000
--- a/ext/json/ext/parser/parser.c
+++ /dev/null
@@ -1,1752 +0,0 @@
-#line 1 "parser.rl"
-/* vim: set cin et sw=4 ts=4: */
-
-#include "ruby.h"
-#include "ruby/re.h"
-#include "ruby/st.h"
-#include "unicode.h"
-
-#define EVIL 0x666
-
-static VALUE mJSON, mExt, cParser, eParserError, eNestingError;
-static VALUE CNaN, CInfinity, CMinusInfinity;
-
-static ID i_json_creatable_p, i_json_create, i_create_id, i_create_additions,
- i_chr, i_max_nesting, i_allow_nan;
-
-#define MinusInfinity "-Infinity"
-
-typedef struct JSON_ParserStruct {
- VALUE Vsource;
- char *source;
- long len;
- char *memo;
- VALUE create_id;
- int max_nesting;
- int current_nesting;
- int allow_nan;
-} JSON_Parser;
-
-static char *JSON_parse_object(JSON_Parser *json, char *p, char *pe, VALUE *result);
-static char *JSON_parse_array(JSON_Parser *json, char *p, char *pe, VALUE *result);
-static char *JSON_parse_value(JSON_Parser *json, char *p, char *pe, VALUE *result);
-static char *JSON_parse_string(JSON_Parser *json, char *p, char *pe, VALUE *result);
-static char *JSON_parse_integer(JSON_Parser *json, char *p, char *pe, VALUE *result);
-static char *JSON_parse_float(JSON_Parser *json, char *p, char *pe, VALUE *result);
-
-#define GET_STRUCT \
- JSON_Parser *json; \
- Data_Get_Struct(self, JSON_Parser, json);
-
-#line 66 "parser.rl"
-
-
-
-#line 46 "parser.c"
-static const int JSON_object_start = 1;
-static const int JSON_object_first_final = 27;
-static const int JSON_object_error = 0;
-
-static const int JSON_object_en_main = 1;
-
-#line 99 "parser.rl"
-
-
-static char *JSON_parse_object(JSON_Parser *json, char *p, char *pe, VALUE *result)
-{
- int cs = EVIL;
- VALUE last_name = Qnil;
-
- if (json->max_nesting && json->current_nesting > json->max_nesting) {
- rb_raise(eNestingError, "nesting of %d is to deep", json->current_nesting);
- }
-
- *result = rb_hash_new();
-
-
-#line 68 "parser.c"
- {
- cs = JSON_object_start;
- }
-#line 113 "parser.rl"
-
-#line 74 "parser.c"
- {
- if ( p == pe )
- goto _out;
- switch ( cs )
- {
-case 1:
- if ( (*p) == 123 )
- goto st2;
- goto st0;
-st0:
- goto _out0;
-st2:
- if ( ++p == pe )
- goto _out2;
-case 2:
- switch( (*p) ) {
- case 13: goto st2;
- case 32: goto st2;
- case 34: goto tr2;
- case 47: goto st23;
- case 125: goto tr4;
- }
- if ( 9 <= (*p) && (*p) <= 10 )
- goto st2;
- goto st0;
-tr2:
-#line 85 "parser.rl"
- {
- char *np = JSON_parse_string(json, p, pe, &last_name);
- if (np == NULL) goto _out3; else {p = (( np))-1;}
- }
- goto st3;
-st3:
- if ( ++p == pe )
- goto _out3;
-case 3:
-#line 111 "parser.c"
- switch( (*p) ) {
- case 13: goto st3;
- case 32: goto st3;
- case 47: goto st4;
- case 58: goto st8;
- }
- if ( 9 <= (*p) && (*p) <= 10 )
- goto st3;
- goto st0;
-st4:
- if ( ++p == pe )
- goto _out4;
-case 4:
- switch( (*p) ) {
- case 42: goto st5;
- case 47: goto st7;
- }
- goto st0;
-st5:
- if ( ++p == pe )
- goto _out5;
-case 5:
- if ( (*p) == 42 )
- goto st6;
- goto st5;
-st6:
- if ( ++p == pe )
- goto _out6;
-case 6:
- switch( (*p) ) {
- case 42: goto st6;
- case 47: goto st3;
- }
- goto st5;
-st7:
- if ( ++p == pe )
- goto _out7;
-case 7:
- if ( (*p) == 10 )
- goto st3;
- goto st7;
-st8:
- if ( ++p == pe )
- goto _out8;
-case 8:
- switch( (*p) ) {
- case 13: goto st8;
- case 32: goto st8;
- case 34: goto tr11;
- case 45: goto tr11;
- case 47: goto st19;
- case 73: goto tr11;
- case 78: goto tr11;
- case 91: goto tr11;
- case 102: goto tr11;
- case 110: goto tr11;
- case 116: goto tr11;
- case 123: goto tr11;
- }
- if ( (*p) > 10 ) {
- if ( 48 <= (*p) && (*p) <= 57 )
- goto tr11;
- } else if ( (*p) >= 9 )
- goto st8;
- goto st0;
-tr11:
-#line 74 "parser.rl"
- {
- VALUE v = Qnil;
- char *np = JSON_parse_value(json, p, pe, &v);
- if (np == NULL) {
- goto _out9;
- } else {
- rb_hash_aset(*result, last_name, v);
- {p = (( np))-1;}
- }
- }
- goto st9;
-st9:
- if ( ++p == pe )
- goto _out9;
-case 9:
-#line 194 "parser.c"
- switch( (*p) ) {
- case 13: goto st9;
- case 32: goto st9;
- case 44: goto st10;
- case 47: goto st15;
- case 125: goto tr4;
- }
- if ( 9 <= (*p) && (*p) <= 10 )
- goto st9;
- goto st0;
-st10:
- if ( ++p == pe )
- goto _out10;
-case 10:
- switch( (*p) ) {
- case 13: goto st10;
- case 32: goto st10;
- case 34: goto tr2;
- case 47: goto st11;
- }
- if ( 9 <= (*p) && (*p) <= 10 )
- goto st10;
- goto st0;
-st11:
- if ( ++p == pe )
- goto _out11;
-case 11:
- switch( (*p) ) {
- case 42: goto st12;
- case 47: goto st14;
- }
- goto st0;
-st12:
- if ( ++p == pe )
- goto _out12;
-case 12:
- if ( (*p) == 42 )
- goto st13;
- goto st12;
-st13:
- if ( ++p == pe )
- goto _out13;
-case 13:
- switch( (*p) ) {
- case 42: goto st13;
- case 47: goto st10;
- }
- goto st12;
-st14:
- if ( ++p == pe )
- goto _out14;
-case 14:
- if ( (*p) == 10 )
- goto st10;
- goto st14;
-st15:
- if ( ++p == pe )
- goto _out15;
-case 15:
- switch( (*p) ) {
- case 42: goto st16;
- case 47: goto st18;
- }
- goto st0;
-st16:
- if ( ++p == pe )
- goto _out16;
-case 16:
- if ( (*p) == 42 )
- goto st17;
- goto st16;
-st17:
- if ( ++p == pe )
- goto _out17;
-case 17:
- switch( (*p) ) {
- case 42: goto st17;
- case 47: goto st9;
- }
- goto st16;
-st18:
- if ( ++p == pe )
- goto _out18;
-case 18:
- if ( (*p) == 10 )
- goto st9;
- goto st18;
-tr4:
-#line 90 "parser.rl"
- { goto _out27; }
- goto st27;
-st27:
- if ( ++p == pe )
- goto _out27;
-case 27:
-#line 290 "parser.c"
- goto st0;
-st19:
- if ( ++p == pe )
- goto _out19;
-case 19:
- switch( (*p) ) {
- case 42: goto st20;
- case 47: goto st22;
- }
- goto st0;
-st20:
- if ( ++p == pe )
- goto _out20;
-case 20:
- if ( (*p) == 42 )
- goto st21;
- goto st20;
-st21:
- if ( ++p == pe )
- goto _out21;
-case 21:
- switch( (*p) ) {
- case 42: goto st21;
- case 47: goto st8;
- }
- goto st20;
-st22:
- if ( ++p == pe )
- goto _out22;
-case 22:
- if ( (*p) == 10 )
- goto st8;
- goto st22;
-st23:
- if ( ++p == pe )
- goto _out23;
-case 23:
- switch( (*p) ) {
- case 42: goto st24;
- case 47: goto st26;
- }
- goto st0;
-st24:
- if ( ++p == pe )
- goto _out24;
-case 24:
- if ( (*p) == 42 )
- goto st25;
- goto st24;
-st25:
- if ( ++p == pe )
- goto _out25;
-case 25:
- switch( (*p) ) {
- case 42: goto st25;
- case 47: goto st2;
- }
- goto st24;
-st26:
- if ( ++p == pe )
- goto _out26;
-case 26:
- if ( (*p) == 10 )
- goto st2;
- goto st26;
- }
- _out0: cs = 0; goto _out;
- _out2: cs = 2; goto _out;
- _out3: cs = 3; goto _out;
- _out4: cs = 4; goto _out;
- _out5: cs = 5; goto _out;
- _out6: cs = 6; goto _out;
- _out7: cs = 7; goto _out;
- _out8: cs = 8; goto _out;
- _out9: cs = 9; goto _out;
- _out10: cs = 10; goto _out;
- _out11: cs = 11; goto _out;
- _out12: cs = 12; goto _out;
- _out13: cs = 13; goto _out;
- _out14: cs = 14; goto _out;
- _out15: cs = 15; goto _out;
- _out16: cs = 16; goto _out;
- _out17: cs = 17; goto _out;
- _out18: cs = 18; goto _out;
- _out27: cs = 27; goto _out;
- _out19: cs = 19; goto _out;
- _out20: cs = 20; goto _out;
- _out21: cs = 21; goto _out;
- _out22: cs = 22; goto _out;
- _out23: cs = 23; goto _out;
- _out24: cs = 24; goto _out;
- _out25: cs = 25; goto _out;
- _out26: cs = 26; goto _out;
-
- _out: {}
- }
-#line 114 "parser.rl"
-
- if (cs >= JSON_object_first_final) {
- if (RTEST(json->create_id)) {
- VALUE klassname = rb_hash_aref(*result, json->create_id);
- if (!NIL_P(klassname)) {
- VALUE klass = rb_path2class(StringValueCStr(klassname));
- if RTEST(rb_funcall(klass, i_json_creatable_p, 0)) {
- *result = rb_funcall(klass, i_json_create, 1, *result);
- }
- }
- }
- return p + 1;
- } else {
- return NULL;
- }
-}
-
-
-#line 406 "parser.c"
-static const int JSON_value_start = 1;
-static const int JSON_value_first_final = 21;
-static const int JSON_value_error = 0;
-
-static const int JSON_value_en_main = 1;
-
-#line 212 "parser.rl"
-
-
-static char *JSON_parse_value(JSON_Parser *json, char *p, char *pe, VALUE *result)
-{
- int cs = EVIL;
-
-
-#line 421 "parser.c"
- {
- cs = JSON_value_start;
- }
-#line 219 "parser.rl"
-
-#line 427 "parser.c"
- {
- if ( p == pe )
- goto _out;
- switch ( cs )
- {
-case 1:
- switch( (*p) ) {
- case 34: goto tr0;
- case 45: goto tr2;
- case 73: goto st2;
- case 78: goto st9;
- case 91: goto tr5;
- case 102: goto st11;
- case 110: goto st15;
- case 116: goto st18;
- case 123: goto tr9;
- }
- if ( 48 <= (*p) && (*p) <= 57 )
- goto tr2;
- goto st0;
-st0:
- goto _out0;
-tr0:
-#line 160 "parser.rl"
- {
- char *np = JSON_parse_string(json, p, pe, result);
- if (np == NULL) goto _out21; else {p = (( np))-1;}
- }
- goto st21;
-tr2:
-#line 165 "parser.rl"
- {
- char *np;
- if(pe > p + 9 && !strncmp(MinusInfinity, p, 9)) {
- if (json->allow_nan) {
- *result = CMinusInfinity;
- {p = (( p + 10))-1;}
- goto _out21;
- } else {
- rb_raise(eParserError, "unexpected token at '%s'", p);
- }
- }
- np = JSON_parse_float(json, p, pe, result);
- if (np != NULL) {p = (( np))-1;}
- np = JSON_parse_integer(json, p, pe, result);
- if (np != NULL) {p = (( np))-1;}
- goto _out21;
- }
- goto st21;
-tr5:
-#line 183 "parser.rl"
- {
- char *np;
- json->current_nesting += 1;
- np = JSON_parse_array(json, p, pe, result);
- json->current_nesting -= 1;
- if (np == NULL) goto _out21; else {p = (( np))-1;}
- }
- goto st21;
-tr9:
-#line 191 "parser.rl"
- {
- char *np;
- json->current_nesting += 1;
- np = JSON_parse_object(json, p, pe, result);
- json->current_nesting -= 1;
- if (np == NULL) goto _out21; else {p = (( np))-1;}
- }
- goto st21;
-tr16:
-#line 153 "parser.rl"
- {
- if (json->allow_nan) {
- *result = CInfinity;
- } else {
- rb_raise(eParserError, "unexpected token at '%s'", p - 8);
- }
- }
- goto st21;
-tr18:
-#line 146 "parser.rl"
- {
- if (json->allow_nan) {
- *result = CNaN;
- } else {
- rb_raise(eParserError, "unexpected token at '%s'", p - 2);
- }
- }
- goto st21;
-tr22:
-#line 140 "parser.rl"
- {
- *result = Qfalse;
- }
- goto st21;
-tr25:
-#line 137 "parser.rl"
- {
- *result = Qnil;
- }
- goto st21;
-tr28:
-#line 143 "parser.rl"
- {
- *result = Qtrue;
- }
- goto st21;
-st21:
- if ( ++p == pe )
- goto _out21;
-case 21:
-#line 199 "parser.rl"
- { goto _out21; }
-#line 541 "parser.c"
- goto st0;
-st2:
- if ( ++p == pe )
- goto _out2;
-case 2:
- if ( (*p) == 110 )
- goto st3;
- goto st0;
-st3:
- if ( ++p == pe )
- goto _out3;
-case 3:
- if ( (*p) == 102 )
- goto st4;
- goto st0;
-st4:
- if ( ++p == pe )
- goto _out4;
-case 4:
- if ( (*p) == 105 )
- goto st5;
- goto st0;
-st5:
- if ( ++p == pe )
- goto _out5;
-case 5:
- if ( (*p) == 110 )
- goto st6;
- goto st0;
-st6:
- if ( ++p == pe )
- goto _out6;
-case 6:
- if ( (*p) == 105 )
- goto st7;
- goto st0;
-st7:
- if ( ++p == pe )
- goto _out7;
-case 7:
- if ( (*p) == 116 )
- goto st8;
- goto st0;
-st8:
- if ( ++p == pe )
- goto _out8;
-case 8:
- if ( (*p) == 121 )
- goto tr16;
- goto st0;
-st9:
- if ( ++p == pe )
- goto _out9;
-case 9:
- if ( (*p) == 97 )
- goto st10;
- goto st0;
-st10:
- if ( ++p == pe )
- goto _out10;
-case 10:
- if ( (*p) == 78 )
- goto tr18;
- goto st0;
-st11:
- if ( ++p == pe )
- goto _out11;
-case 11:
- if ( (*p) == 97 )
- goto st12;
- goto st0;
-st12:
- if ( ++p == pe )
- goto _out12;
-case 12:
- if ( (*p) == 108 )
- goto st13;
- goto st0;
-st13:
- if ( ++p == pe )
- goto _out13;
-case 13:
- if ( (*p) == 115 )
- goto st14;
- goto st0;
-st14:
- if ( ++p == pe )
- goto _out14;
-case 14:
- if ( (*p) == 101 )
- goto tr22;
- goto st0;
-st15:
- if ( ++p == pe )
- goto _out15;
-case 15:
- if ( (*p) == 117 )
- goto st16;
- goto st0;
-st16:
- if ( ++p == pe )
- goto _out16;
-case 16:
- if ( (*p) == 108 )
- goto st17;
- goto st0;
-st17:
- if ( ++p == pe )
- goto _out17;
-case 17:
- if ( (*p) == 108 )
- goto tr25;
- goto st0;
-st18:
- if ( ++p == pe )
- goto _out18;
-case 18:
- if ( (*p) == 114 )
- goto st19;
- goto st0;
-st19:
- if ( ++p == pe )
- goto _out19;
-case 19:
- if ( (*p) == 117 )
- goto st20;
- goto st0;
-st20:
- if ( ++p == pe )
- goto _out20;
-case 20:
- if ( (*p) == 101 )
- goto tr28;
- goto st0;
- }
- _out0: cs = 0; goto _out;
- _out21: cs = 21; goto _out;
- _out2: cs = 2; goto _out;
- _out3: cs = 3; goto _out;
- _out4: cs = 4; goto _out;
- _out5: cs = 5; goto _out;
- _out6: cs = 6; goto _out;
- _out7: cs = 7; goto _out;
- _out8: cs = 8; goto _out;
- _out9: cs = 9; goto _out;
- _out10: cs = 10; goto _out;
- _out11: cs = 11; goto _out;
- _out12: cs = 12; goto _out;
- _out13: cs = 13; goto _out;
- _out14: cs = 14; goto _out;
- _out15: cs = 15; goto _out;
- _out16: cs = 16; goto _out;
- _out17: cs = 17; goto _out;
- _out18: cs = 18; goto _out;
- _out19: cs = 19; goto _out;
- _out20: cs = 20; goto _out;
-
- _out: {}
- }
-#line 220 "parser.rl"
-
- if (cs >= JSON_value_first_final) {
- return p;
- } else {
- return NULL;
- }
-}
-
-
-#line 711 "parser.c"
-static const int JSON_integer_start = 1;
-static const int JSON_integer_first_final = 5;
-static const int JSON_integer_error = 0;
-
-static const int JSON_integer_en_main = 1;
-
-#line 236 "parser.rl"
-
-
-static char *JSON_parse_integer(JSON_Parser *json, char *p, char *pe, VALUE *result)
-{
- int cs = EVIL;
-
-
-#line 726 "parser.c"
- {
- cs = JSON_integer_start;
- }
-#line 243 "parser.rl"
- json->memo = p;
-
-#line 733 "parser.c"
- {
- if ( p == pe )
- goto _out;
- switch ( cs )
- {
-case 1:
- switch( (*p) ) {
- case 45: goto st2;
- case 48: goto st3;
- }
- if ( 49 <= (*p) && (*p) <= 57 )
- goto st4;
- goto st0;
-st0:
- goto _out0;
-st2:
- if ( ++p == pe )
- goto _out2;
-case 2:
- if ( (*p) == 48 )
- goto st3;
- if ( 49 <= (*p) && (*p) <= 57 )
- goto st4;
- goto st0;
-st3:
- if ( ++p == pe )
- goto _out3;
-case 3:
- if ( 48 <= (*p) && (*p) <= 57 )
- goto st0;
- goto tr4;
-tr4:
-#line 233 "parser.rl"
- { goto _out5; }
- goto st5;
-st5:
- if ( ++p == pe )
- goto _out5;
-case 5:
-#line 773 "parser.c"
- goto st0;
-st4:
- if ( ++p == pe )
- goto _out4;
-case 4:
- if ( 48 <= (*p) && (*p) <= 57 )
- goto st4;
- goto tr4;
- }
- _out0: cs = 0; goto _out;
- _out2: cs = 2; goto _out;
- _out3: cs = 3; goto _out;
- _out5: cs = 5; goto _out;
- _out4: cs = 4; goto _out;
-
- _out: {}
- }
-#line 245 "parser.rl"
-
- if (cs >= JSON_integer_first_final) {
- long len = p - json->memo;
- *result = rb_Integer(rb_str_new(json->memo, len));
- return p + 1;
- } else {
- return NULL;
- }
-}
-
-
-#line 803 "parser.c"
-static const int JSON_float_start = 1;
-static const int JSON_float_first_final = 10;
-static const int JSON_float_error = 0;
-
-static const int JSON_float_en_main = 1;
-
-#line 267 "parser.rl"
-
-
-static char *JSON_parse_float(JSON_Parser *json, char *p, char *pe, VALUE *result)
-{
- int cs = EVIL;
-
-
-#line 818 "parser.c"
- {
- cs = JSON_float_start;
- }
-#line 274 "parser.rl"
- json->memo = p;
-
-#line 825 "parser.c"
- {
- if ( p == pe )
- goto _out;
- switch ( cs )
- {
-case 1:
- switch( (*p) ) {
- case 45: goto st2;
- case 48: goto st3;
- }
- if ( 49 <= (*p) && (*p) <= 57 )
- goto st9;
- goto st0;
-st0:
- goto _out0;
-st2:
- if ( ++p == pe )
- goto _out2;
-case 2:
- if ( (*p) == 48 )
- goto st3;
- if ( 49 <= (*p) && (*p) <= 57 )
- goto st9;
- goto st0;
-st3:
- if ( ++p == pe )
- goto _out3;
-case 3:
- switch( (*p) ) {
- case 46: goto st4;
- case 69: goto st6;
- case 101: goto st6;
- }
- goto st0;
-st4:
- if ( ++p == pe )
- goto _out4;
-case 4:
- if ( 48 <= (*p) && (*p) <= 57 )
- goto st5;
- goto st0;
-st5:
- if ( ++p == pe )
- goto _out5;
-case 5:
- switch( (*p) ) {
- case 69: goto st6;
- case 101: goto st6;
- }
- if ( (*p) > 46 ) {
- if ( 48 <= (*p) && (*p) <= 57 )
- goto st5;
- } else if ( (*p) >= 45 )
- goto st0;
- goto tr7;
-tr7:
-#line 261 "parser.rl"
- { goto _out10; }
- goto st10;
-st10:
- if ( ++p == pe )
- goto _out10;
-case 10:
-#line 889 "parser.c"
- goto st0;
-st6:
- if ( ++p == pe )
- goto _out6;
-case 6:
- switch( (*p) ) {
- case 43: goto st7;
- case 45: goto st7;
- }
- if ( 48 <= (*p) && (*p) <= 57 )
- goto st8;
- goto st0;
-st7:
- if ( ++p == pe )
- goto _out7;
-case 7:
- if ( 48 <= (*p) && (*p) <= 57 )
- goto st8;
- goto st0;
-st8:
- if ( ++p == pe )
- goto _out8;
-case 8:
- switch( (*p) ) {
- case 69: goto st0;
- case 101: goto st0;
- }
- if ( (*p) > 46 ) {
- if ( 48 <= (*p) && (*p) <= 57 )
- goto st8;
- } else if ( (*p) >= 45 )
- goto st0;
- goto tr7;
-st9:
- if ( ++p == pe )
- goto _out9;
-case 9:
- switch( (*p) ) {
- case 46: goto st4;
- case 69: goto st6;
- case 101: goto st6;
- }
- if ( 48 <= (*p) && (*p) <= 57 )
- goto st9;
- goto st0;
- }
- _out0: cs = 0; goto _out;
- _out2: cs = 2; goto _out;
- _out3: cs = 3; goto _out;
- _out4: cs = 4; goto _out;
- _out5: cs = 5; goto _out;
- _out10: cs = 10; goto _out;
- _out6: cs = 6; goto _out;
- _out7: cs = 7; goto _out;
- _out8: cs = 8; goto _out;
- _out9: cs = 9; goto _out;
-
- _out: {}
- }
-#line 276 "parser.rl"
-
- if (cs >= JSON_float_first_final) {
- long len = p - json->memo;
- *result = rb_Float(rb_str_new(json->memo, len));
- return p + 1;
- } else {
- return NULL;
- }
-}
-
-
-
-#line 962 "parser.c"
-static const int JSON_array_start = 1;
-static const int JSON_array_first_final = 17;
-static const int JSON_array_error = 0;
-
-static const int JSON_array_en_main = 1;
-
-#line 312 "parser.rl"
-
-
-static char *JSON_parse_array(JSON_Parser *json, char *p, char *pe, VALUE *result)
-{
- int cs = EVIL;
-
- if (json->max_nesting && json->current_nesting > json->max_nesting) {
- rb_raise(eNestingError, "nesting of %d is to deep", json->current_nesting);
- }
- *result = rb_ary_new();
-
-
-#line 982 "parser.c"
- {
- cs = JSON_array_start;
- }
-#line 324 "parser.rl"
-
-#line 988 "parser.c"
- {
- if ( p == pe )
- goto _out;
- switch ( cs )
- {
-case 1:
- if ( (*p) == 91 )
- goto st2;
- goto st0;
-st0:
- goto _out0;
-st2:
- if ( ++p == pe )
- goto _out2;
-case 2:
- switch( (*p) ) {
- case 13: goto st2;
- case 32: goto st2;
- case 34: goto tr2;
- case 45: goto tr2;
- case 47: goto st13;
- case 73: goto tr2;
- case 78: goto tr2;
- case 91: goto tr2;
- case 93: goto tr4;
- case 102: goto tr2;
- case 110: goto tr2;
- case 116: goto tr2;
- case 123: goto tr2;
- }
- if ( (*p) > 10 ) {
- if ( 48 <= (*p) && (*p) <= 57 )
- goto tr2;
- } else if ( (*p) >= 9 )
- goto st2;
- goto st0;
-tr2:
-#line 293 "parser.rl"
- {
- VALUE v = Qnil;
- char *np = JSON_parse_value(json, p, pe, &v);
- if (np == NULL) {
- goto _out3;
- } else {
- rb_ary_push(*result, v);
- {p = (( np))-1;}
- }
- }
- goto st3;
-st3:
- if ( ++p == pe )
- goto _out3;
-case 3:
-#line 1042 "parser.c"
- switch( (*p) ) {
- case 13: goto st3;
- case 32: goto st3;
- case 44: goto st4;
- case 47: goto st9;
- case 93: goto tr4;
- }
- if ( 9 <= (*p) && (*p) <= 10 )
- goto st3;
- goto st0;
-st4:
- if ( ++p == pe )
- goto _out4;
-case 4:
- switch( (*p) ) {
- case 13: goto st4;
- case 32: goto st4;
- case 34: goto tr2;
- case 45: goto tr2;
- case 47: goto st5;
- case 73: goto tr2;
- case 78: goto tr2;
- case 91: goto tr2;
- case 102: goto tr2;
- case 110: goto tr2;
- case 116: goto tr2;
- case 123: goto tr2;
- }
- if ( (*p) > 10 ) {
- if ( 48 <= (*p) && (*p) <= 57 )
- goto tr2;
- } else if ( (*p) >= 9 )
- goto st4;
- goto st0;
-st5:
- if ( ++p == pe )
- goto _out5;
-case 5:
- switch( (*p) ) {
- case 42: goto st6;
- case 47: goto st8;
- }
- goto st0;
-st6:
- if ( ++p == pe )
- goto _out6;
-case 6:
- if ( (*p) == 42 )
- goto st7;
- goto st6;
-st7:
- if ( ++p == pe )
- goto _out7;
-case 7:
- switch( (*p) ) {
- case 42: goto st7;
- case 47: goto st4;
- }
- goto st6;
-st8:
- if ( ++p == pe )
- goto _out8;
-case 8:
- if ( (*p) == 10 )
- goto st4;
- goto st8;
-st9:
- if ( ++p == pe )
- goto _out9;
-case 9:
- switch( (*p) ) {
- case 42: goto st10;
- case 47: goto st12;
- }
- goto st0;
-st10:
- if ( ++p == pe )
- goto _out10;
-case 10:
- if ( (*p) == 42 )
- goto st11;
- goto st10;
-st11:
- if ( ++p == pe )
- goto _out11;
-case 11:
- switch( (*p) ) {
- case 42: goto st11;
- case 47: goto st3;
- }
- goto st10;
-st12:
- if ( ++p == pe )
- goto _out12;
-case 12:
- if ( (*p) == 10 )
- goto st3;
- goto st12;
-tr4:
-#line 304 "parser.rl"
- { goto _out17; }
- goto st17;
-st17:
- if ( ++p == pe )
- goto _out17;
-case 17:
-#line 1149 "parser.c"
- goto st0;
-st13:
- if ( ++p == pe )
- goto _out13;
-case 13:
- switch( (*p) ) {
- case 42: goto st14;
- case 47: goto st16;
- }
- goto st0;
-st14:
- if ( ++p == pe )
- goto _out14;
-case 14:
- if ( (*p) == 42 )
- goto st15;
- goto st14;
-st15:
- if ( ++p == pe )
- goto _out15;
-case 15:
- switch( (*p) ) {
- case 42: goto st15;
- case 47: goto st2;
- }
- goto st14;
-st16:
- if ( ++p == pe )
- goto _out16;
-case 16:
- if ( (*p) == 10 )
- goto st2;
- goto st16;
- }
- _out0: cs = 0; goto _out;
- _out2: cs = 2; goto _out;
- _out3: cs = 3; goto _out;
- _out4: cs = 4; goto _out;
- _out5: cs = 5; goto _out;
- _out6: cs = 6; goto _out;
- _out7: cs = 7; goto _out;
- _out8: cs = 8; goto _out;
- _out9: cs = 9; goto _out;
- _out10: cs = 10; goto _out;
- _out11: cs = 11; goto _out;
- _out12: cs = 12; goto _out;
- _out17: cs = 17; goto _out;
- _out13: cs = 13; goto _out;
- _out14: cs = 14; goto _out;
- _out15: cs = 15; goto _out;
- _out16: cs = 16; goto _out;
-
- _out: {}
- }
-#line 325 "parser.rl"
-
- if(cs >= JSON_array_first_final) {
- return p + 1;
- } else {
- rb_raise(eParserError, "unexpected token at '%s'", p);
- }
-}
-
-static VALUE json_string_unescape(char *p, char *pe)
-{
- VALUE result = rb_str_buf_new(pe - p + 1);
-
- while (p < pe) {
- if (*p == '\\') {
- p++;
- if (p >= pe) return Qnil; /* raise an exception later, \ at end */
- switch (*p) {
- case '"':
- case '\\':
- rb_str_buf_cat(result, p, 1);
- p++;
- break;
- case 'b':
- rb_str_buf_cat2(result, "\b");
- p++;
- break;
- case 'f':
- rb_str_buf_cat2(result, "\f");
- p++;
- break;
- case 'n':
- rb_str_buf_cat2(result, "\n");
- p++;
- break;
- case 'r':
- rb_str_buf_cat2(result, "\r");
- p++;
- break;
- case 't':
- rb_str_buf_cat2(result, "\t");
- p++;
- break;
- case 'u':
- if (p > pe - 4) {
- return Qnil;
- } else {
- p = JSON_convert_UTF16_to_UTF8(result, p, pe, strictConversion);
- }
- break;
- default:
- rb_str_buf_cat(result, p, 1);
- p++;
- break;
- }
- } else {
- char *q = p;
- while (*q != '\\' && q < pe) q++;
- rb_str_buf_cat(result, p, q - p);
- p = q;
- }
- }
- return result;
-}
-
-
-#line 1270 "parser.c"
-static const int JSON_string_start = 1;
-static const int JSON_string_first_final = 8;
-static const int JSON_string_error = 0;
-
-static const int JSON_string_en_main = 1;
-
-#line 403 "parser.rl"
-
-
-static char *JSON_parse_string(JSON_Parser *json, char *p, char *pe, VALUE *result)
-{
- int cs = EVIL;
-
- *result = rb_str_new("", 0);
-
-#line 1286 "parser.c"
- {
- cs = JSON_string_start;
- }
-#line 411 "parser.rl"
- json->memo = p;
-
-#line 1293 "parser.c"
- {
- if ( p == pe )
- goto _out;
- switch ( cs )
- {
-case 1:
- if ( (*p) == 34 )
- goto st2;
- goto st0;
-st0:
- goto _out0;
-st2:
- if ( ++p == pe )
- goto _out2;
-case 2:
- switch( (*p) ) {
- case 34: goto tr2;
- case 92: goto st3;
- }
- if ( 0 <= (*p) && (*p) <= 31 )
- goto st0;
- goto st2;
-tr2:
-#line 395 "parser.rl"
- {
- *result = json_string_unescape(json->memo + 1, p);
- if (NIL_P(*result)) goto _out8; else {p = (( p + 1))-1;}
- }
-#line 400 "parser.rl"
- { goto _out8; }
- goto st8;
-st8:
- if ( ++p == pe )
- goto _out8;
-case 8:
-#line 1329 "parser.c"
- goto st0;
-st3:
- if ( ++p == pe )
- goto _out3;
-case 3:
- if ( (*p) == 117 )
- goto st4;
- if ( 0 <= (*p) && (*p) <= 31 )
- goto st0;
- goto st2;
-st4:
- if ( ++p == pe )
- goto _out4;
-case 4:
- if ( (*p) < 65 ) {
- if ( 48 <= (*p) && (*p) <= 57 )
- goto st5;
- } else if ( (*p) > 70 ) {
- if ( 97 <= (*p) && (*p) <= 102 )
- goto st5;
- } else
- goto st5;
- goto st0;
-st5:
- if ( ++p == pe )
- goto _out5;
-case 5:
- if ( (*p) < 65 ) {
- if ( 48 <= (*p) && (*p) <= 57 )
- goto st6;
- } else if ( (*p) > 70 ) {
- if ( 97 <= (*p) && (*p) <= 102 )
- goto st6;
- } else
- goto st6;
- goto st0;
-st6:
- if ( ++p == pe )
- goto _out6;
-case 6:
- if ( (*p) < 65 ) {
- if ( 48 <= (*p) && (*p) <= 57 )
- goto st7;
- } else if ( (*p) > 70 ) {
- if ( 97 <= (*p) && (*p) <= 102 )
- goto st7;
- } else
- goto st7;
- goto st0;
-st7:
- if ( ++p == pe )
- goto _out7;
-case 7:
- if ( (*p) < 65 ) {
- if ( 48 <= (*p) && (*p) <= 57 )
- goto st2;
- } else if ( (*p) > 70 ) {
- if ( 97 <= (*p) && (*p) <= 102 )
- goto st2;
- } else
- goto st2;
- goto st0;
- }
- _out0: cs = 0; goto _out;
- _out2: cs = 2; goto _out;
- _out8: cs = 8; goto _out;
- _out3: cs = 3; goto _out;
- _out4: cs = 4; goto _out;
- _out5: cs = 5; goto _out;
- _out6: cs = 6; goto _out;
- _out7: cs = 7; goto _out;
-
- _out: {}
- }
-#line 413 "parser.rl"
-
- if (cs >= JSON_string_first_final) {
- return p + 1;
- } else {
- return NULL;
- }
-}
-
-
-
-#line 1415 "parser.c"
-static const int JSON_start = 1;
-static const int JSON_first_final = 10;
-static const int JSON_error = 0;
-
-static const int JSON_en_main = 1;
-
-#line 447 "parser.rl"
-
-
-/*
- * Document-class: JSON::Ext::Parser
- *
- * This is the JSON parser implemented as a C extension. It can be configured
- * to be used by setting
- *
- * JSON.parser = JSON::Ext::Parser
- *
- * with the method parser= in JSON.
- *
- */
-
-/*
- * call-seq: new(source, opts => {})
- *
- * Creates a new JSON::Ext::Parser instance for the string _source_.
- *
- * Creates a new JSON::Ext::Parser instance for the string _source_.
- *
- * It will be configured by the _opts_ hash. _opts_ can have the following
- * keys:
- *
- * _opts_ can have the following keys:
- * * *max_nesting*: The maximum depth of nesting allowed in the parsed data
- * structures. Disable depth checking with :max_nesting => false|nil|0, it
- * defaults to 19.
- * * *allow_nan*: If set to true, allow NaN, Infinity and -Infinity in
- * defiance of RFC 4627 to be parsed by the Parser. This option defaults to
- * false.
- * * *create_additions*: If set to false, the Parser doesn't create
- * additions even if a matchin class and create_id was found. This option
- * defaults to true.
- */
-static VALUE cParser_initialize(int argc, VALUE *argv, VALUE self)
-{
- char *ptr;
- long len;
- VALUE source, opts;
- GET_STRUCT;
- rb_scan_args(argc, argv, "11", &source, &opts);
- source = StringValue(source);
- ptr = RSTRING_PTR(source);
- len = RSTRING_LEN(source);
- if (len < 2) {
- rb_raise(eParserError, "A JSON text must at least contain two octets!");
- }
- if (!NIL_P(opts)) {
- opts = rb_convert_type(opts, T_HASH, "Hash", "to_hash");
- if (NIL_P(opts)) {
- rb_raise(rb_eArgError, "opts needs to be like a hash");
- } else {
- VALUE tmp = ID2SYM(i_max_nesting);
- if (st_lookup(RHASH_TBL(opts), tmp, 0)) {
- VALUE max_nesting = rb_hash_aref(opts, tmp);
- if (RTEST(max_nesting)) {
- Check_Type(max_nesting, T_FIXNUM);
- json->max_nesting = FIX2INT(max_nesting);
- } else {
- json->max_nesting = 0;
- }
- } else {
- json->max_nesting = 19;
- }
- tmp = ID2SYM(i_allow_nan);
- if (st_lookup(RHASH_TBL(opts), tmp, 0)) {
- VALUE allow_nan = rb_hash_aref(opts, tmp);
- json->allow_nan = RTEST(allow_nan) ? 1 : 0;
- } else {
- json->allow_nan = 0;
- }
- tmp = ID2SYM(i_create_additions);
- if (st_lookup(RHASH_TBL(opts), tmp, 0)) {
- VALUE create_additions = rb_hash_aref(opts, tmp);
- if (RTEST(create_additions)) {
- json->create_id = rb_funcall(mJSON, i_create_id, 0);
- } else {
- json->create_id = Qnil;
- }
- } else {
- json->create_id = rb_funcall(mJSON, i_create_id, 0);
- }
- }
- } else {
- json->max_nesting = 19;
- json->allow_nan = 0;
- json->create_id = rb_funcall(mJSON, i_create_id, 0);
- }
- json->current_nesting = 0;
- /*
- Convert these?
- if (len >= 4 && ptr[0] == 0 && ptr[1] == 0 && ptr[2] == 0) {
- rb_raise(eParserError, "Only UTF8 octet streams are supported atm!");
- } else if (len >= 4 && ptr[0] == 0 && ptr[2] == 0) {
- rb_raise(eParserError, "Only UTF8 octet streams are supported atm!");
- } else if (len >= 4 && ptr[1] == 0 && ptr[2] == 0 && ptr[3] == 0) {
- rb_raise(eParserError, "Only UTF8 octet streams are supported atm!");
- } else if (len >= 4 && ptr[1] == 0 && ptr[3] == 0) {
- rb_raise(eParserError, "Only UTF8 octet streams are supported atm!");
- }
- */
- json->len = len;
- json->source = ptr;
- json->Vsource = source;
- return self;
-}
-
-/*
- * call-seq: parse()
- *
- * Parses the current JSON text _source_ and returns the complete data
- * structure as a result.
- */
-static VALUE cParser_parse(VALUE self)
-{
- char *p, *pe;
- int cs = EVIL;
- VALUE result = Qnil;
- GET_STRUCT;
-
-
-#line 1545 "parser.c"
- {
- cs = JSON_start;
- }
-#line 569 "parser.rl"
- p = json->source;
- pe = p + json->len;
-
-#line 1553 "parser.c"
- {
- if ( p == pe )
- goto _out;
- switch ( cs )
- {
-st1:
- if ( ++p == pe )
- goto _out1;
-case 1:
- switch( (*p) ) {
- case 13: goto st1;
- case 32: goto st1;
- case 47: goto st2;
- case 91: goto tr3;
- case 123: goto tr4;
- }
- if ( 9 <= (*p) && (*p) <= 10 )
- goto st1;
- goto st0;
-st0:
- goto _out0;
-st2:
- if ( ++p == pe )
- goto _out2;
-case 2:
- switch( (*p) ) {
- case 42: goto st3;
- case 47: goto st5;
- }
- goto st0;
-st3:
- if ( ++p == pe )
- goto _out3;
-case 3:
- if ( (*p) == 42 )
- goto st4;
- goto st3;
-st4:
- if ( ++p == pe )
- goto _out4;
-case 4:
- switch( (*p) ) {
- case 42: goto st4;
- case 47: goto st1;
- }
- goto st3;
-st5:
- if ( ++p == pe )
- goto _out5;
-case 5:
- if ( (*p) == 10 )
- goto st1;
- goto st5;
-tr3:
-#line 436 "parser.rl"
- {
- char *np;
- json->current_nesting = 1;
- np = JSON_parse_array(json, p, pe, &result);
- if (np == NULL) goto _out10; else {p = (( np))-1;}
- }
- goto st10;
-tr4:
-#line 429 "parser.rl"
- {
- char *np;
- json->current_nesting = 1;
- np = JSON_parse_object(json, p, pe, &result);
- if (np == NULL) goto _out10; else {p = (( np))-1;}
- }
- goto st10;
-st10:
- if ( ++p == pe )
- goto _out10;
-case 10:
-#line 1629 "parser.c"
- switch( (*p) ) {
- case 13: goto st10;
- case 32: goto st10;
- case 47: goto st6;
- }
- if ( 9 <= (*p) && (*p) <= 10 )
- goto st10;
- goto st0;
-st6:
- if ( ++p == pe )
- goto _out6;
-case 6:
- switch( (*p) ) {
- case 42: goto st7;
- case 47: goto st9;
- }
- goto st0;
-st7:
- if ( ++p == pe )
- goto _out7;
-case 7:
- if ( (*p) == 42 )
- goto st8;
- goto st7;
-st8:
- if ( ++p == pe )
- goto _out8;
-case 8:
- switch( (*p) ) {
- case 42: goto st8;
- case 47: goto st10;
- }
- goto st7;
-st9:
- if ( ++p == pe )
- goto _out9;
-case 9:
- if ( (*p) == 10 )
- goto st10;
- goto st9;
- }
- _out1: cs = 1; goto _out;
- _out0: cs = 0; goto _out;
- _out2: cs = 2; goto _out;
- _out3: cs = 3; goto _out;
- _out4: cs = 4; goto _out;
- _out5: cs = 5; goto _out;
- _out10: cs = 10; goto _out;
- _out6: cs = 6; goto _out;
- _out7: cs = 7; goto _out;
- _out8: cs = 8; goto _out;
- _out9: cs = 9; goto _out;
-
- _out: {}
- }
-#line 572 "parser.rl"
-
- if (cs >= JSON_first_final && p == pe) {
- return result;
- } else {
- rb_raise(eParserError, "unexpected token at '%s'", p);
- }
-}
-
-inline static JSON_Parser *JSON_allocate()
-{
- JSON_Parser *json = ALLOC(JSON_Parser);
- MEMZERO(json, JSON_Parser, 1);
- return json;
-}
-
-static void JSON_mark(JSON_Parser *json)
-{
- rb_gc_mark_maybe(json->Vsource);
- rb_gc_mark_maybe(json->create_id);
-}
-
-static void JSON_free(JSON_Parser *json)
-{
- ruby_xfree(json);
-}
-
-static VALUE cJSON_parser_s_allocate(VALUE klass)
-{
- JSON_Parser *json = JSON_allocate();
- return Data_Wrap_Struct(klass, JSON_mark, JSON_free, json);
-}
-
-/*
- * call-seq: source()
- *
- * Returns a copy of the current _source_ string, that was used to construct
- * this Parser.
- */
-static VALUE cParser_source(VALUE self)
-{
- GET_STRUCT;
- return rb_str_dup(json->Vsource);
-}
-
-void Init_parser()
-{
- mJSON = rb_define_module("JSON");
- mExt = rb_define_module_under(mJSON, "Ext");
- cParser = rb_define_class_under(mExt, "Parser", rb_cObject);
- eParserError = rb_path2class("JSON::ParserError");
- eNestingError = rb_path2class("JSON::NestingError");
- rb_define_alloc_func(cParser, cJSON_parser_s_allocate);
- rb_define_method(cParser, "initialize", cParser_initialize, -1);
- rb_define_method(cParser, "parse", cParser_parse, 0);
- rb_define_method(cParser, "source", cParser_source, 0);
-
- CNaN = rb_const_get(mJSON, rb_intern("NaN"));
- CInfinity = rb_const_get(mJSON, rb_intern("Infinity"));
- CMinusInfinity = rb_const_get(mJSON, rb_intern("MinusInfinity"));
-
- i_json_creatable_p = rb_intern("json_creatable?");
- i_json_create = rb_intern("json_create");
- i_create_id = rb_intern("create_id");
- i_create_additions = rb_intern("create_additions");
- i_chr = rb_intern("chr");
- i_max_nesting = rb_intern("max_nesting");
- i_allow_nan = rb_intern("allow_nan");
-}
diff --git a/ext/json/ext/parser/parser.rl b/ext/json/ext/parser/parser.rl
deleted file mode 100644
index 840b8e0f10..0000000000
--- a/ext/json/ext/parser/parser.rl
+++ /dev/null
@@ -1,639 +0,0 @@
-/* vim: set cin et sw=4 ts=4: */
-
-#include "ruby.h"
-#include "ruby/re.h"
-#include "ruby/st.h"
-#include "unicode.h"
-
-#define EVIL 0x666
-
-static VALUE mJSON, mExt, cParser, eParserError, eNestingError;
-static VALUE CNaN, CInfinity, CMinusInfinity;
-
-static ID i_json_creatable_p, i_json_create, i_create_id, i_create_additions,
- i_chr, i_max_nesting, i_allow_nan;
-
-#define MinusInfinity "-Infinity"
-
-typedef struct JSON_ParserStruct {
- VALUE Vsource;
- char *source;
- long len;
- char *memo;
- VALUE create_id;
- int max_nesting;
- int current_nesting;
- int allow_nan;
-} JSON_Parser;
-
-static char *JSON_parse_object(JSON_Parser *json, char *p, char *pe, VALUE *result);
-static char *JSON_parse_array(JSON_Parser *json, char *p, char *pe, VALUE *result);
-static char *JSON_parse_value(JSON_Parser *json, char *p, char *pe, VALUE *result);
-static char *JSON_parse_string(JSON_Parser *json, char *p, char *pe, VALUE *result);
-static char *JSON_parse_integer(JSON_Parser *json, char *p, char *pe, VALUE *result);
-static char *JSON_parse_float(JSON_Parser *json, char *p, char *pe, VALUE *result);
-
-#define GET_STRUCT \
- JSON_Parser *json; \
- Data_Get_Struct(self, JSON_Parser, json);
-
-%%{
- machine JSON_common;
-
- cr = '\n';
- cr_neg = [^\n];
- ws = [ \t\r\n];
- c_comment = '/*' ( any* - (any* '*/' any* ) ) '*/';
- cpp_comment = '//' cr_neg* cr;
- comment = c_comment | cpp_comment;
- ignore = ws | comment;
- name_separator = ':';
- value_separator = ',';
- Vnull = 'null';
- Vfalse = 'false';
- Vtrue = 'true';
- VNaN = 'NaN';
- VInfinity = 'Infinity';
- VMinusInfinity = '-Infinity';
- begin_value = [nft"\-[{NI] | digit;
- begin_object = '{';
- end_object = '}';
- begin_array = '[';
- end_array = ']';
- begin_string = '"';
- begin_name = begin_string;
- begin_number = digit | '-';
-}%%
-
-%%{
- machine JSON_object;
- include JSON_common;
-
- write data;
-
- action parse_value {
- VALUE v = Qnil;
- char *np = JSON_parse_value(json, fpc, pe, &v);
- if (np == NULL) {
- fbreak;
- } else {
- rb_hash_aset(*result, last_name, v);
- fexec np;
- }
- }
-
- action parse_name {
- char *np = JSON_parse_string(json, fpc, pe, &last_name);
- if (np == NULL) fbreak; else fexec np;
- }
-
- action exit { fbreak; }
-
- a_pair = ignore* begin_name >parse_name
- ignore* name_separator ignore*
- begin_value >parse_value;
-
- main := begin_object
- (a_pair (ignore* value_separator a_pair)*)?
- ignore* end_object @exit;
-}%%
-
-static char *JSON_parse_object(JSON_Parser *json, char *p, char *pe, VALUE *result)
-{
- int cs = EVIL;
- VALUE last_name = Qnil;
-
- if (json->max_nesting && json->current_nesting > json->max_nesting) {
- rb_raise(eNestingError, "nesting of %d is to deep", json->current_nesting);
- }
-
- *result = rb_hash_new();
-
- %% write init;
- %% write exec;
-
- if (cs >= JSON_object_first_final) {
- if (RTEST(json->create_id)) {
- VALUE klassname = rb_hash_aref(*result, json->create_id);
- if (!NIL_P(klassname)) {
- VALUE klass = rb_path2class(StringValueCStr(klassname));
- if RTEST(rb_funcall(klass, i_json_creatable_p, 0)) {
- *result = rb_funcall(klass, i_json_create, 1, *result);
- }
- }
- }
- return p + 1;
- } else {
- return NULL;
- }
-}
-
-%%{
- machine JSON_value;
- include JSON_common;
-
- write data;
-
- action parse_null {
- *result = Qnil;
- }
- action parse_false {
- *result = Qfalse;
- }
- action parse_true {
- *result = Qtrue;
- }
- action parse_nan {
- if (json->allow_nan) {
- *result = CNaN;
- } else {
- rb_raise(eParserError, "unexpected token at '%s'", p - 2);
- }
- }
- action parse_infinity {
- if (json->allow_nan) {
- *result = CInfinity;
- } else {
- rb_raise(eParserError, "unexpected token at '%s'", p - 8);
- }
- }
- action parse_string {
- char *np = JSON_parse_string(json, fpc, pe, result);
- if (np == NULL) fbreak; else fexec np;
- }
-
- action parse_number {
- char *np;
- if(pe > fpc + 9 && !strncmp(MinusInfinity, fpc, 9)) {
- if (json->allow_nan) {
- *result = CMinusInfinity;
- fexec p + 10;
- fbreak;
- } else {
- rb_raise(eParserError, "unexpected token at '%s'", p);
- }
- }
- np = JSON_parse_float(json, fpc, pe, result);
- if (np != NULL) fexec np;
- np = JSON_parse_integer(json, fpc, pe, result);
- if (np != NULL) fexec np;
- fbreak;
- }
-
- action parse_array {
- char *np;
- json->current_nesting += 1;
- np = JSON_parse_array(json, fpc, pe, result);
- json->current_nesting -= 1;
- if (np == NULL) fbreak; else fexec np;
- }
-
- action parse_object {
- char *np;
- json->current_nesting += 1;
- np = JSON_parse_object(json, fpc, pe, result);
- json->current_nesting -= 1;
- if (np == NULL) fbreak; else fexec np;
- }
-
- action exit { fbreak; }
-
-main := (
- Vnull @parse_null |
- Vfalse @parse_false |
- Vtrue @parse_true |
- VNaN @parse_nan |
- VInfinity @parse_infinity |
- begin_number >parse_number |
- begin_string >parse_string |
- begin_array >parse_array |
- begin_object >parse_object
- ) %*exit;
-}%%
-
-static char *JSON_parse_value(JSON_Parser *json, char *p, char *pe, VALUE *result)
-{
- int cs = EVIL;
-
- %% write init;
- %% write exec;
-
- if (cs >= JSON_value_first_final) {
- return p;
- } else {
- return NULL;
- }
-}
-
-%%{
- machine JSON_integer;
-
- write data;
-
- action exit { fbreak; }
-
- main := '-'? ('0' | [1-9][0-9]*) (^[0-9] @exit);
-}%%
-
-static char *JSON_parse_integer(JSON_Parser *json, char *p, char *pe, VALUE *result)
-{
- int cs = EVIL;
-
- %% write init;
- json->memo = p;
- %% write exec;
-
- if (cs >= JSON_integer_first_final) {
- long len = p - json->memo;
- *result = rb_Integer(rb_str_new(json->memo, len));
- return p + 1;
- } else {
- return NULL;
- }
-}
-
-%%{
- machine JSON_float;
- include JSON_common;
-
- write data;
-
- action exit { fbreak; }
-
- main := '-'? (
- (('0' | [1-9][0-9]*) '.' [0-9]+ ([Ee] [+\-]?[0-9]+)?)
- | (('0' | [1-9][0-9]*) ([Ee] [+\-]?[0-9]+))
- ) (^[0-9Ee.\-] @exit );
-}%%
-
-static char *JSON_parse_float(JSON_Parser *json, char *p, char *pe, VALUE *result)
-{
- int cs = EVIL;
-
- %% write init;
- json->memo = p;
- %% write exec;
-
- if (cs >= JSON_float_first_final) {
- long len = p - json->memo;
- *result = rb_Float(rb_str_new(json->memo, len));
- return p + 1;
- } else {
- return NULL;
- }
-}
-
-
-%%{
- machine JSON_array;
- include JSON_common;
-
- write data;
-
- action parse_value {
- VALUE v = Qnil;
- char *np = JSON_parse_value(json, fpc, pe, &v);
- if (np == NULL) {
- fbreak;
- } else {
- rb_ary_push(*result, v);
- fexec np;
- }
- }
-
- action exit { fbreak; }
-
- next_element = value_separator ignore* begin_value >parse_value;
-
- main := begin_array ignore*
- ((begin_value >parse_value ignore*)
- (ignore* next_element ignore*)*)?
- end_array @exit;
-}%%
-
-static char *JSON_parse_array(JSON_Parser *json, char *p, char *pe, VALUE *result)
-{
- int cs = EVIL;
-
- if (json->max_nesting && json->current_nesting > json->max_nesting) {
- rb_raise(eNestingError, "nesting of %d is to deep", json->current_nesting);
- }
- *result = rb_ary_new();
-
- %% write init;
- %% write exec;
-
- if(cs >= JSON_array_first_final) {
- return p + 1;
- } else {
- rb_raise(eParserError, "unexpected token at '%s'", p);
- }
-}
-
-static VALUE json_string_unescape(char *p, char *pe)
-{
- VALUE result = rb_str_buf_new(pe - p + 1);
-
- while (p < pe) {
- if (*p == '\\') {
- p++;
- if (p >= pe) return Qnil; /* raise an exception later, \ at end */
- switch (*p) {
- case '"':
- case '\\':
- rb_str_buf_cat(result, p, 1);
- p++;
- break;
- case 'b':
- rb_str_buf_cat2(result, "\b");
- p++;
- break;
- case 'f':
- rb_str_buf_cat2(result, "\f");
- p++;
- break;
- case 'n':
- rb_str_buf_cat2(result, "\n");
- p++;
- break;
- case 'r':
- rb_str_buf_cat2(result, "\r");
- p++;
- break;
- case 't':
- rb_str_buf_cat2(result, "\t");
- p++;
- break;
- case 'u':
- if (p > pe - 4) {
- return Qnil;
- } else {
- p = JSON_convert_UTF16_to_UTF8(result, p, pe, strictConversion);
- }
- break;
- default:
- rb_str_buf_cat(result, p, 1);
- p++;
- break;
- }
- } else {
- char *q = p;
- while (*q != '\\' && q < pe) q++;
- rb_str_buf_cat(result, p, q - p);
- p = q;
- }
- }
- return result;
-}
-
-%%{
- machine JSON_string;
- include JSON_common;
-
- write data;
-
- action parse_string {
- *result = json_string_unescape(json->memo + 1, p);
- if (NIL_P(*result)) fbreak; else fexec p + 1;
- }
-
- action exit { fbreak; }
-
- main := '"' ((^(["\\] | 0..0x1f) | '\\'["\\/bfnrt] | '\\u'[0-9a-fA-F]{4} | '\\'^(["\\/bfnrtu]|0..0x1f))* %parse_string) '"' @exit;
-}%%
-
-static char *JSON_parse_string(JSON_Parser *json, char *p, char *pe, VALUE *result)
-{
- int cs = EVIL;
-
- *result = rb_str_new("", 0);
- %% write init;
- json->memo = p;
- %% write exec;
-
- if (cs >= JSON_string_first_final) {
- return p + 1;
- } else {
- return NULL;
- }
-}
-
-
-%%{
- machine JSON;
-
- write data;
-
- include JSON_common;
-
- action parse_object {
- char *np;
- json->current_nesting = 1;
- np = JSON_parse_object(json, fpc, pe, &result);
- if (np == NULL) fbreak; else fexec np;
- }
-
- action parse_array {
- char *np;
- json->current_nesting = 1;
- np = JSON_parse_array(json, fpc, pe, &result);
- if (np == NULL) fbreak; else fexec np;
- }
-
- main := ignore* (
- begin_object >parse_object |
- begin_array >parse_array
- ) ignore*;
-}%%
-
-/*
- * Document-class: JSON::Ext::Parser
- *
- * This is the JSON parser implemented as a C extension. It can be configured
- * to be used by setting
- *
- * JSON.parser = JSON::Ext::Parser
- *
- * with the method parser= in JSON.
- *
- */
-
-/*
- * call-seq: new(source, opts => {})
- *
- * Creates a new JSON::Ext::Parser instance for the string _source_.
- *
- * Creates a new JSON::Ext::Parser instance for the string _source_.
- *
- * It will be configured by the _opts_ hash. _opts_ can have the following
- * keys:
- *
- * _opts_ can have the following keys:
- * * *max_nesting*: The maximum depth of nesting allowed in the parsed data
- * structures. Disable depth checking with :max_nesting => false|nil|0, it
- * defaults to 19.
- * * *allow_nan*: If set to true, allow NaN, Infinity and -Infinity in
- * defiance of RFC 4627 to be parsed by the Parser. This option defaults to
- * false.
- * * *create_additions*: If set to false, the Parser doesn't create
- * additions even if a matchin class and create_id was found. This option
- * defaults to true.
- */
-static VALUE cParser_initialize(int argc, VALUE *argv, VALUE self)
-{
- char *ptr;
- long len;
- VALUE source, opts;
- GET_STRUCT;
- rb_scan_args(argc, argv, "11", &source, &opts);
- source = StringValue(source);
- ptr = RSTRING_PTR(source);
- len = RSTRING_LEN(source);
- if (len < 2) {
- rb_raise(eParserError, "A JSON text must at least contain two octets!");
- }
- if (!NIL_P(opts)) {
- opts = rb_convert_type(opts, T_HASH, "Hash", "to_hash");
- if (NIL_P(opts)) {
- rb_raise(rb_eArgError, "opts needs to be like a hash");
- } else {
- VALUE tmp = ID2SYM(i_max_nesting);
- if (st_lookup(RHASH_TBL(opts), tmp, 0)) {
- VALUE max_nesting = rb_hash_aref(opts, tmp);
- if (RTEST(max_nesting)) {
- Check_Type(max_nesting, T_FIXNUM);
- json->max_nesting = FIX2INT(max_nesting);
- } else {
- json->max_nesting = 0;
- }
- } else {
- json->max_nesting = 19;
- }
- tmp = ID2SYM(i_allow_nan);
- if (st_lookup(RHASH_TBL(opts), tmp, 0)) {
- VALUE allow_nan = rb_hash_aref(opts, tmp);
- json->allow_nan = RTEST(allow_nan) ? 1 : 0;
- } else {
- json->allow_nan = 0;
- }
- tmp = ID2SYM(i_create_additions);
- if (st_lookup(RHASH_TBL(opts), tmp, 0)) {
- VALUE create_additions = rb_hash_aref(opts, tmp);
- if (RTEST(create_additions)) {
- json->create_id = rb_funcall(mJSON, i_create_id, 0);
- } else {
- json->create_id = Qnil;
- }
- } else {
- json->create_id = rb_funcall(mJSON, i_create_id, 0);
- }
- }
- } else {
- json->max_nesting = 19;
- json->allow_nan = 0;
- json->create_id = rb_funcall(mJSON, i_create_id, 0);
- }
- json->current_nesting = 0;
- /*
- Convert these?
- if (len >= 4 && ptr[0] == 0 && ptr[1] == 0 && ptr[2] == 0) {
- rb_raise(eParserError, "Only UTF8 octet streams are supported atm!");
- } else if (len >= 4 && ptr[0] == 0 && ptr[2] == 0) {
- rb_raise(eParserError, "Only UTF8 octet streams are supported atm!");
- } else if (len >= 4 && ptr[1] == 0 && ptr[2] == 0 && ptr[3] == 0) {
- rb_raise(eParserError, "Only UTF8 octet streams are supported atm!");
- } else if (len >= 4 && ptr[1] == 0 && ptr[3] == 0) {
- rb_raise(eParserError, "Only UTF8 octet streams are supported atm!");
- }
- */
- json->len = len;
- json->source = ptr;
- json->Vsource = source;
- return self;
-}
-
-/*
- * call-seq: parse()
- *
- * Parses the current JSON text _source_ and returns the complete data
- * structure as a result.
- */
-static VALUE cParser_parse(VALUE self)
-{
- char *p, *pe;
- int cs = EVIL;
- VALUE result = Qnil;
- GET_STRUCT;
-
- %% write init;
- p = json->source;
- pe = p + json->len;
- %% write exec;
-
- if (cs >= JSON_first_final && p == pe) {
- return result;
- } else {
- rb_raise(eParserError, "unexpected token at '%s'", p);
- }
-}
-
-inline static JSON_Parser *JSON_allocate()
-{
- JSON_Parser *json = ALLOC(JSON_Parser);
- MEMZERO(json, JSON_Parser, 1);
- return json;
-}
-
-static void JSON_mark(JSON_Parser *json)
-{
- rb_gc_mark_maybe(json->Vsource);
- rb_gc_mark_maybe(json->create_id);
-}
-
-static void JSON_free(JSON_Parser *json)
-{
- free(json);
-}
-
-static VALUE cJSON_parser_s_allocate(VALUE klass)
-{
- JSON_Parser *json = JSON_allocate();
- return Data_Wrap_Struct(klass, JSON_mark, JSON_free, json);
-}
-
-/*
- * call-seq: source()
- *
- * Returns a copy of the current _source_ string, that was used to construct
- * this Parser.
- */
-static VALUE cParser_source(VALUE self)
-{
- GET_STRUCT;
- return rb_str_dup(json->Vsource);
-}
-
-void Init_parser()
-{
- mJSON = rb_define_module("JSON");
- mExt = rb_define_module_under(mJSON, "Ext");
- cParser = rb_define_class_under(mExt, "Parser", rb_cObject);
- eParserError = rb_path2class("JSON::ParserError");
- eNestingError = rb_path2class("JSON::NestingError");
- rb_define_alloc_func(cParser, cJSON_parser_s_allocate);
- rb_define_method(cParser, "initialize", cParser_initialize, -1);
- rb_define_method(cParser, "parse", cParser_parse, 0);
- rb_define_method(cParser, "source", cParser_source, 0);
-
- CNaN = rb_const_get(mJSON, rb_intern("NaN"));
- CInfinity = rb_const_get(mJSON, rb_intern("Infinity"));
- CMinusInfinity = rb_const_get(mJSON, rb_intern("MinusInfinity"));
-
- i_json_creatable_p = rb_intern("json_creatable?");
- i_json_create = rb_intern("json_create");
- i_create_id = rb_intern("create_id");
- i_create_additions = rb_intern("create_additions");
- i_chr = rb_intern("chr");
- i_max_nesting = rb_intern("max_nesting");
- i_allow_nan = rb_intern("allow_nan");
-}
diff --git a/ext/json/ext/parser/unicode.c b/ext/json/ext/parser/unicode.c
deleted file mode 100644
index f196727354..0000000000
--- a/ext/json/ext/parser/unicode.c
+++ /dev/null
@@ -1,156 +0,0 @@
-/* vim: set cin et sw=4 ts=4: */
-
-#include "unicode.h"
-
-/*
- * Copyright 2001-2004 Unicode, Inc.
- *
- * Disclaimer
- *
- * This source code is provided as is by Unicode, Inc. No claims are
- * made as to fitness for any particular purpose. No warranties of any
- * kind are expressed or implied. The recipient agrees to determine
- * applicability of information provided. If this file has been
- * purchased on magnetic or optical media from Unicode, Inc., the
- * sole remedy for any claim will be exchange of defective media
- * within 90 days of receipt.
- *
- * Limitations on Rights to Redistribute This Code
- *
- * Unicode, Inc. hereby grants the right to freely use the information
- * supplied in this file in the creation of products supporting the
- * Unicode Standard, and to make copies of this file in any form
- * for internal or external distribution as long as this notice
- * remains attached.
- */
-
-/*
- * Index into the table below with the first byte of a UTF-8 sequence to
- * get the number of trailing bytes that are supposed to follow it.
- * Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is
- * left as-is for anyone who may want to do such conversion, which was
- * allowed in earlier algorithms.
- */
-static const char trailingBytesForUTF8[256] = {
- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
- 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
- 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5
-};
-
-/*
- * Magic values subtracted from a buffer value during UTF8 conversion.
- * This table contains as many values as there might be trailing bytes
- * in a UTF-8 sequence.
- */
-static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL,
- 0x03C82080UL, 0xFA082080UL, 0x82082080UL };
-
-/*
- * Once the bits are split out into bytes of UTF-8, this is a mask OR-ed
- * into the first byte, depending on how many bytes follow. There are
- * as many entries in this table as there are UTF-8 sequence types.
- * (I.e., one byte sequence, two byte... etc.). Remember that sequencs
- * for *legal* UTF-8 will be 4 or fewer bytes total.
- */
-static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
-
-char *JSON_convert_UTF16_to_UTF8 (
- VALUE buffer,
- char *source,
- char *sourceEnd,
- ConversionFlags flags)
-{
- UTF16 *tmp, *tmpPtr, *tmpEnd;
- char buf[5];
- long n = 0, i;
- char *p = source - 1;
-
- while (p < sourceEnd && p[0] == '\\' && p[1] == 'u') {
- p += 6;
- n++;
- }
- p = source + 1;
- buf[4] = 0;
- tmpPtr = tmp = ALLOC_N(UTF16, n);
- tmpEnd = tmp + n;
- for (i = 0; i < n; i++) {
- buf[0] = *p++;
- buf[1] = *p++;
- buf[2] = *p++;
- buf[3] = *p++;
- tmpPtr[i] = strtol(buf, NULL, 16);
- p += 2;
- }
-
- while (tmpPtr < tmpEnd) {
- UTF32 ch;
- unsigned short bytesToWrite = 0;
- const UTF32 byteMask = 0xBF;
- const UTF32 byteMark = 0x80;
- ch = *tmpPtr++;
- /* If we have a surrogate pair, convert to UTF32 first. */
- if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) {
- /* If the 16 bits following the high surrogate are in the source
- * buffer... */
- if (tmpPtr < tmpEnd) {
- UTF32 ch2 = *tmpPtr;
- /* If it's a low surrogate, convert to UTF32. */
- if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) {
- ch = ((ch - UNI_SUR_HIGH_START) << halfShift)
- + (ch2 - UNI_SUR_LOW_START) + halfBase;
- ++tmpPtr;
- } else if (flags == strictConversion) { /* it's an unpaired high surrogate */
- ruby_xfree(tmp);
- rb_raise(rb_path2class("JSON::ParserError"),
- "source sequence is illegal/malformed near %s", source);
- }
- } else { /* We don't have the 16 bits following the high surrogate. */
- ruby_xfree(tmp);
- rb_raise(rb_path2class("JSON::ParserError"),
- "partial character in source, but hit end near %s", source);
- break;
- }
- } else if (flags == strictConversion) {
- /* UTF-16 surrogate values are illegal in UTF-32 */
- if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) {
- ruby_xfree(tmp);
- rb_raise(rb_path2class("JSON::ParserError"),
- "source sequence is illegal/malformed near %s", source);
- }
- }
- /* Figure out how many bytes the result will require */
- if (ch < (UTF32) 0x80) {
- bytesToWrite = 1;
- } else if (ch < (UTF32) 0x800) {
- bytesToWrite = 2;
- } else if (ch < (UTF32) 0x10000) {
- bytesToWrite = 3;
- } else if (ch < (UTF32) 0x110000) {
- bytesToWrite = 4;
- } else {
- bytesToWrite = 3;
- ch = UNI_REPLACEMENT_CHAR;
- }
-
- buf[0] = 0;
- buf[1] = 0;
- buf[2] = 0;
- buf[3] = 0;
- p = buf + bytesToWrite;
- switch (bytesToWrite) { /* note: everything falls through. */
- case 4: *--p = (UTF8) ((ch | byteMark) & byteMask); ch >>= 6;
- case 3: *--p = (UTF8) ((ch | byteMark) & byteMask); ch >>= 6;
- case 2: *--p = (UTF8) ((ch | byteMark) & byteMask); ch >>= 6;
- case 1: *--p = (UTF8) (ch | firstByteMark[bytesToWrite]);
- }
- rb_str_buf_cat(buffer, p, bytesToWrite);
- }
- ruby_xfree(tmp);
- source += 5 + (n - 1) * 6;
- return source;
-}
diff --git a/ext/json/ext/parser/unicode.h b/ext/json/ext/parser/unicode.h
deleted file mode 100644
index 155da0ceee..0000000000
--- a/ext/json/ext/parser/unicode.h
+++ /dev/null
@@ -1,58 +0,0 @@
-
-#ifndef _PARSER_UNICODE_H_
-#define _PARSER_UNICODE_H_
-
-#include "ruby.h"
-
-typedef unsigned long UTF32; /* at least 32 bits */
-typedef unsigned short UTF16; /* at least 16 bits */
-typedef unsigned char UTF8; /* typically 8 bits */
-
-#define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD
-#define UNI_MAX_BMP (UTF32)0x0000FFFF
-#define UNI_MAX_UTF16 (UTF32)0x0010FFFF
-#define UNI_MAX_UTF32 (UTF32)0x7FFFFFFF
-#define UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF
-
-#define UNI_SUR_HIGH_START (UTF32)0xD800
-#define UNI_SUR_HIGH_END (UTF32)0xDBFF
-#define UNI_SUR_LOW_START (UTF32)0xDC00
-#define UNI_SUR_LOW_END (UTF32)0xDFFF
-
-static const int halfShift = 10; /* used for shifting by 10 bits */
-
-static const UTF32 halfBase = 0x0010000UL;
-static const UTF32 halfMask = 0x3FFUL;
-
-typedef enum {
- conversionOK = 0, /* conversion successful */
- sourceExhausted, /* partial character in source, but hit end */
- targetExhausted, /* insuff. room in target for conversion */
- sourceIllegal /* source sequence is illegal/malformed */
-} ConversionResult;
-
-typedef enum {
- strictConversion = 0,
- lenientConversion
-} ConversionFlags;
-
-char *JSON_convert_UTF16_to_UTF8 (
- VALUE buffer,
- char *source,
- char *sourceEnd,
- ConversionFlags flags);
-
-#ifndef RARRAY_PTR
-#define RARRAY_PTR(ARRAY) RARRAY(ARRAY)->ptr
-#endif
-#ifndef RARRAY_LEN
-#define RARRAY_LEN(ARRAY) RARRAY(ARRAY)->len
-#endif
-#ifndef RSTRING_PTR
-#define RSTRING_PTR(string) RSTRING(string)->ptr
-#endif
-#ifndef RSTRING_LEN
-#define RSTRING_LEN(string) RSTRING(string)->len
-#endif
-
-#endif
diff --git a/ext/json/extconf.rb b/ext/json/extconf.rb
index 850798c643..8a99b6a5c8 100644
--- a/ext/json/extconf.rb
+++ b/ext/json/extconf.rb
@@ -1,3 +1,3 @@
require 'mkmf'
-create_makefile('json')
+create_makefile('json')
diff --git a/ext/json/fbuffer/fbuffer.h b/ext/json/fbuffer/fbuffer.h
new file mode 100644
index 0000000000..b84a073554
--- /dev/null
+++ b/ext/json/fbuffer/fbuffer.h
@@ -0,0 +1,260 @@
+#ifndef _FBUFFER_H_
+#define _FBUFFER_H_
+
+#include "../json.h"
+#include "../vendor/jeaiii-ltoa.h"
+
+enum fbuffer_type {
+ FBUFFER_HEAP_ALLOCATED = 0,
+ FBUFFER_STACK_ALLOCATED = 1,
+};
+
+typedef struct FBufferStruct {
+ enum fbuffer_type type;
+ size_t initial_length;
+ size_t len;
+ size_t capa;
+#if JSON_DEBUG
+ size_t requested;
+#endif
+ char *ptr;
+ VALUE io;
+} FBuffer;
+
+#define FBUFFER_STACK_SIZE 512
+#define FBUFFER_IO_BUFFER_SIZE (16384 - 1)
+#define FBUFFER_INITIAL_LENGTH_DEFAULT 1024
+
+#define FBUFFER_PTR(fb) ((fb)->ptr)
+#define FBUFFER_LEN(fb) ((fb)->len)
+#define FBUFFER_CAPA(fb) ((fb)->capa)
+#define FBUFFER_PAIR(fb) FBUFFER_PTR(fb), FBUFFER_LEN(fb)
+
+static void fbuffer_free(FBuffer *fb);
+static void fbuffer_clear(FBuffer *fb);
+static void fbuffer_append(FBuffer *fb, const char *newstr, size_t len);
+static void fbuffer_append_long(FBuffer *fb, long number);
+static inline void fbuffer_append_char(FBuffer *fb, char newchr);
+static VALUE fbuffer_finalize(FBuffer *fb);
+
+static void fbuffer_stack_init(FBuffer *fb, size_t initial_length, char *stack_buffer, size_t stack_buffer_size)
+{
+ fb->initial_length = (initial_length > 0) ? initial_length : FBUFFER_INITIAL_LENGTH_DEFAULT;
+ if (stack_buffer) {
+ fb->type = FBUFFER_STACK_ALLOCATED;
+ fb->ptr = stack_buffer;
+ fb->capa = stack_buffer_size;
+ }
+#if JSON_DEBUG
+ fb->requested = 0;
+#endif
+}
+
+static inline void fbuffer_consumed(FBuffer *fb, size_t consumed)
+{
+#if JSON_DEBUG
+ if (consumed > fb->requested) {
+ rb_bug("fbuffer: Out of bound write");
+ }
+ fb->requested = 0;
+#endif
+ fb->len += consumed;
+}
+
+static void fbuffer_free(FBuffer *fb)
+{
+ if (fb->ptr && fb->type == FBUFFER_HEAP_ALLOCATED) {
+ ruby_xfree(fb->ptr);
+ }
+}
+
+static void fbuffer_clear(FBuffer *fb)
+{
+ fb->len = 0;
+}
+
+static void fbuffer_flush(FBuffer *fb)
+{
+ rb_io_write(fb->io, rb_utf8_str_new(fb->ptr, fb->len));
+ fbuffer_clear(fb);
+}
+
+static void fbuffer_realloc(FBuffer *fb, size_t required)
+{
+ if (required > fb->capa) {
+ if (fb->type == FBUFFER_STACK_ALLOCATED) {
+ const char *old_buffer = fb->ptr;
+ fb->ptr = ALLOC_N(char, required);
+ fb->type = FBUFFER_HEAP_ALLOCATED;
+ MEMCPY(fb->ptr, old_buffer, char, fb->len);
+ } else {
+ REALLOC_N(fb->ptr, char, required);
+ }
+ fb->capa = required;
+ }
+}
+
+static void fbuffer_do_inc_capa(FBuffer *fb, size_t requested)
+{
+ if (RB_UNLIKELY(fb->io)) {
+ if (fb->capa < FBUFFER_IO_BUFFER_SIZE) {
+ fbuffer_realloc(fb, FBUFFER_IO_BUFFER_SIZE);
+ } else {
+ fbuffer_flush(fb);
+ }
+
+ if (RB_LIKELY(requested < fb->capa)) {
+ return;
+ }
+ }
+
+ size_t required;
+
+ if (RB_UNLIKELY(!fb->ptr)) {
+ fb->ptr = ALLOC_N(char, fb->initial_length);
+ fb->capa = fb->initial_length;
+ }
+
+ for (required = fb->capa; requested > required - fb->len; required <<= 1);
+
+ fbuffer_realloc(fb, required);
+}
+
+static inline void fbuffer_inc_capa(FBuffer *fb, size_t requested)
+{
+#if JSON_DEBUG
+ fb->requested = requested;
+#endif
+
+ if (RB_UNLIKELY(requested > fb->capa - fb->len)) {
+ fbuffer_do_inc_capa(fb, requested);
+ }
+}
+
+static inline size_t fbuffer_size_mul_or_raise(size_t a, size_t b)
+{
+ size_t result = a * b;
+ if (RB_UNLIKELY(a != 0 && (result / a) != b)) {
+ rb_raise(rb_eArgError, "Buffer overflow, the resulting document is too large to be generated");
+ }
+ return result;
+}
+
+static inline void fbuffer_append_reserved(FBuffer *fb, const char *newstr, size_t len)
+{
+ MEMCPY(fb->ptr + fb->len, newstr, char, len);
+ fbuffer_consumed(fb, len);
+}
+
+static inline void fbuffer_append(FBuffer *fb, const char *newstr, size_t len)
+{
+ if (len > 0) {
+ fbuffer_inc_capa(fb, len);
+ fbuffer_append_reserved(fb, newstr, len);
+ }
+}
+
+/* Appends a character into a buffer. The buffer needs to have sufficient capacity, via fbuffer_inc_capa(...). */
+static inline void fbuffer_append_reserved_char(FBuffer *fb, char chr)
+{
+#if JSON_DEBUG
+ if (fb->requested < 1) {
+ rb_bug("fbuffer: unreserved write");
+ }
+ fb->requested--;
+#endif
+
+ fb->ptr[fb->len] = chr;
+ fb->len++;
+}
+
+static void fbuffer_append_str(FBuffer *fb, VALUE str)
+{
+ const char *ptr;
+ size_t len;
+ RSTRING_GETMEM(str, ptr, len);
+
+ fbuffer_append(fb, ptr, len);
+ RB_GC_GUARD(str);
+}
+
+static void fbuffer_append_str_repeat(FBuffer *fb, VALUE str, size_t repeat)
+{
+ const char *ptr;
+ size_t len;
+ RSTRING_GETMEM(str, ptr, len);
+
+ fbuffer_inc_capa(fb, fbuffer_size_mul_or_raise(repeat, len));
+ while (repeat) {
+#if JSON_DEBUG
+ fb->requested = len;
+#endif
+ fbuffer_append_reserved(fb, ptr, len);
+ repeat--;
+ }
+ RB_GC_GUARD(str);
+}
+
+static inline void fbuffer_append_char(FBuffer *fb, char newchr)
+{
+ fbuffer_inc_capa(fb, 1);
+ *(fb->ptr + fb->len) = newchr;
+ fbuffer_consumed(fb, 1);
+}
+
+static inline char *fbuffer_cursor(FBuffer *fb)
+{
+ return fb->ptr + fb->len;
+}
+
+static inline void fbuffer_advance_to(FBuffer *fb, char *end)
+{
+ fbuffer_consumed(fb, (end - fb->ptr) - fb->len);
+}
+
+/*
+ * Appends the decimal string representation of \a number into the buffer.
+ */
+static void fbuffer_append_long(FBuffer *fb, long number)
+{
+ /*
+ * The jeaiii_ultoa() function produces digits left-to-right,
+ * allowing us to write directly into the buffer, but we don't know
+ * the number of resulting characters.
+ *
+ * We do know, however, that the `number` argument is always in the
+ * range 0xc000000000000000 to 0x3fffffffffffffff, or, in decimal,
+ * -4611686018427387904 to 4611686018427387903. The max number of chars
+ * generated is therefore 20 (including a potential sign character).
+ */
+
+ static const int MAX_CHARS_FOR_LONG = 20;
+
+ fbuffer_inc_capa(fb, MAX_CHARS_FOR_LONG);
+
+ if (number < 0) {
+ fbuffer_append_reserved_char(fb, '-');
+
+ /*
+ * Since number is always > LONG_MIN, `-number` will not overflow
+ * and is always the positive abs() value.
+ */
+ number = -number;
+ }
+
+ char *end = jeaiii_ultoa(fbuffer_cursor(fb), number);
+ fbuffer_advance_to(fb, end);
+}
+
+static VALUE fbuffer_finalize(FBuffer *fb)
+{
+ if (fb->io) {
+ fbuffer_flush(fb);
+ rb_io_flush(fb->io);
+ return fb->io;
+ } else {
+ return rb_utf8_str_new(FBUFFER_PTR(fb), FBUFFER_LEN(fb));
+ }
+}
+
+#endif // _FBUFFER_H_
diff --git a/ext/json/generator/depend b/ext/json/generator/depend
new file mode 100644
index 0000000000..3ba4acfdd2
--- /dev/null
+++ b/ext/json/generator/depend
@@ -0,0 +1,186 @@
+$(OBJS): $(ruby_headers)
+generator.o: generator.c $(srcdir)/../fbuffer/fbuffer.h
+
+# AUTOGENERATED DEPENDENCIES START
+generator.o: $(RUBY_EXTCONF_H)
+generator.o: $(arch_hdrdir)/ruby/config.h
+generator.o: $(hdrdir)/ruby.h
+generator.o: $(hdrdir)/ruby/assert.h
+generator.o: $(hdrdir)/ruby/backward.h
+generator.o: $(hdrdir)/ruby/backward/2/assume.h
+generator.o: $(hdrdir)/ruby/backward/2/attributes.h
+generator.o: $(hdrdir)/ruby/backward/2/bool.h
+generator.o: $(hdrdir)/ruby/backward/2/inttypes.h
+generator.o: $(hdrdir)/ruby/backward/2/limits.h
+generator.o: $(hdrdir)/ruby/backward/2/long_long.h
+generator.o: $(hdrdir)/ruby/backward/2/stdalign.h
+generator.o: $(hdrdir)/ruby/backward/2/stdarg.h
+generator.o: $(hdrdir)/ruby/defines.h
+generator.o: $(hdrdir)/ruby/encoding.h
+generator.o: $(hdrdir)/ruby/intern.h
+generator.o: $(hdrdir)/ruby/internal/abi.h
+generator.o: $(hdrdir)/ruby/internal/anyargs.h
+generator.o: $(hdrdir)/ruby/internal/arithmetic.h
+generator.o: $(hdrdir)/ruby/internal/arithmetic/char.h
+generator.o: $(hdrdir)/ruby/internal/arithmetic/double.h
+generator.o: $(hdrdir)/ruby/internal/arithmetic/fixnum.h
+generator.o: $(hdrdir)/ruby/internal/arithmetic/gid_t.h
+generator.o: $(hdrdir)/ruby/internal/arithmetic/int.h
+generator.o: $(hdrdir)/ruby/internal/arithmetic/intptr_t.h
+generator.o: $(hdrdir)/ruby/internal/arithmetic/long.h
+generator.o: $(hdrdir)/ruby/internal/arithmetic/long_long.h
+generator.o: $(hdrdir)/ruby/internal/arithmetic/mode_t.h
+generator.o: $(hdrdir)/ruby/internal/arithmetic/off_t.h
+generator.o: $(hdrdir)/ruby/internal/arithmetic/pid_t.h
+generator.o: $(hdrdir)/ruby/internal/arithmetic/short.h
+generator.o: $(hdrdir)/ruby/internal/arithmetic/size_t.h
+generator.o: $(hdrdir)/ruby/internal/arithmetic/st_data_t.h
+generator.o: $(hdrdir)/ruby/internal/arithmetic/uid_t.h
+generator.o: $(hdrdir)/ruby/internal/assume.h
+generator.o: $(hdrdir)/ruby/internal/attr/alloc_size.h
+generator.o: $(hdrdir)/ruby/internal/attr/artificial.h
+generator.o: $(hdrdir)/ruby/internal/attr/cold.h
+generator.o: $(hdrdir)/ruby/internal/attr/const.h
+generator.o: $(hdrdir)/ruby/internal/attr/constexpr.h
+generator.o: $(hdrdir)/ruby/internal/attr/deprecated.h
+generator.o: $(hdrdir)/ruby/internal/attr/diagnose_if.h
+generator.o: $(hdrdir)/ruby/internal/attr/enum_extensibility.h
+generator.o: $(hdrdir)/ruby/internal/attr/error.h
+generator.o: $(hdrdir)/ruby/internal/attr/flag_enum.h
+generator.o: $(hdrdir)/ruby/internal/attr/forceinline.h
+generator.o: $(hdrdir)/ruby/internal/attr/format.h
+generator.o: $(hdrdir)/ruby/internal/attr/maybe_unused.h
+generator.o: $(hdrdir)/ruby/internal/attr/noalias.h
+generator.o: $(hdrdir)/ruby/internal/attr/nodiscard.h
+generator.o: $(hdrdir)/ruby/internal/attr/noexcept.h
+generator.o: $(hdrdir)/ruby/internal/attr/noinline.h
+generator.o: $(hdrdir)/ruby/internal/attr/nonnull.h
+generator.o: $(hdrdir)/ruby/internal/attr/noreturn.h
+generator.o: $(hdrdir)/ruby/internal/attr/packed_struct.h
+generator.o: $(hdrdir)/ruby/internal/attr/pure.h
+generator.o: $(hdrdir)/ruby/internal/attr/restrict.h
+generator.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h
+generator.o: $(hdrdir)/ruby/internal/attr/warning.h
+generator.o: $(hdrdir)/ruby/internal/attr/weakref.h
+generator.o: $(hdrdir)/ruby/internal/cast.h
+generator.o: $(hdrdir)/ruby/internal/compiler_is.h
+generator.o: $(hdrdir)/ruby/internal/compiler_is/apple.h
+generator.o: $(hdrdir)/ruby/internal/compiler_is/clang.h
+generator.o: $(hdrdir)/ruby/internal/compiler_is/gcc.h
+generator.o: $(hdrdir)/ruby/internal/compiler_is/intel.h
+generator.o: $(hdrdir)/ruby/internal/compiler_is/msvc.h
+generator.o: $(hdrdir)/ruby/internal/compiler_is/sunpro.h
+generator.o: $(hdrdir)/ruby/internal/compiler_since.h
+generator.o: $(hdrdir)/ruby/internal/config.h
+generator.o: $(hdrdir)/ruby/internal/constant_p.h
+generator.o: $(hdrdir)/ruby/internal/core.h
+generator.o: $(hdrdir)/ruby/internal/core/rarray.h
+generator.o: $(hdrdir)/ruby/internal/core/rbasic.h
+generator.o: $(hdrdir)/ruby/internal/core/rbignum.h
+generator.o: $(hdrdir)/ruby/internal/core/rclass.h
+generator.o: $(hdrdir)/ruby/internal/core/rdata.h
+generator.o: $(hdrdir)/ruby/internal/core/rfile.h
+generator.o: $(hdrdir)/ruby/internal/core/rhash.h
+generator.o: $(hdrdir)/ruby/internal/core/rmatch.h
+generator.o: $(hdrdir)/ruby/internal/core/robject.h
+generator.o: $(hdrdir)/ruby/internal/core/rregexp.h
+generator.o: $(hdrdir)/ruby/internal/core/rstring.h
+generator.o: $(hdrdir)/ruby/internal/core/rstruct.h
+generator.o: $(hdrdir)/ruby/internal/core/rtypeddata.h
+generator.o: $(hdrdir)/ruby/internal/ctype.h
+generator.o: $(hdrdir)/ruby/internal/dllexport.h
+generator.o: $(hdrdir)/ruby/internal/dosish.h
+generator.o: $(hdrdir)/ruby/internal/encoding/coderange.h
+generator.o: $(hdrdir)/ruby/internal/encoding/ctype.h
+generator.o: $(hdrdir)/ruby/internal/encoding/encoding.h
+generator.o: $(hdrdir)/ruby/internal/encoding/pathname.h
+generator.o: $(hdrdir)/ruby/internal/encoding/re.h
+generator.o: $(hdrdir)/ruby/internal/encoding/sprintf.h
+generator.o: $(hdrdir)/ruby/internal/encoding/string.h
+generator.o: $(hdrdir)/ruby/internal/encoding/symbol.h
+generator.o: $(hdrdir)/ruby/internal/encoding/transcode.h
+generator.o: $(hdrdir)/ruby/internal/error.h
+generator.o: $(hdrdir)/ruby/internal/eval.h
+generator.o: $(hdrdir)/ruby/internal/event.h
+generator.o: $(hdrdir)/ruby/internal/fl_type.h
+generator.o: $(hdrdir)/ruby/internal/gc.h
+generator.o: $(hdrdir)/ruby/internal/glob.h
+generator.o: $(hdrdir)/ruby/internal/globals.h
+generator.o: $(hdrdir)/ruby/internal/has/attribute.h
+generator.o: $(hdrdir)/ruby/internal/has/builtin.h
+generator.o: $(hdrdir)/ruby/internal/has/c_attribute.h
+generator.o: $(hdrdir)/ruby/internal/has/cpp_attribute.h
+generator.o: $(hdrdir)/ruby/internal/has/declspec_attribute.h
+generator.o: $(hdrdir)/ruby/internal/has/extension.h
+generator.o: $(hdrdir)/ruby/internal/has/feature.h
+generator.o: $(hdrdir)/ruby/internal/has/warning.h
+generator.o: $(hdrdir)/ruby/internal/intern/array.h
+generator.o: $(hdrdir)/ruby/internal/intern/bignum.h
+generator.o: $(hdrdir)/ruby/internal/intern/class.h
+generator.o: $(hdrdir)/ruby/internal/intern/compar.h
+generator.o: $(hdrdir)/ruby/internal/intern/complex.h
+generator.o: $(hdrdir)/ruby/internal/intern/cont.h
+generator.o: $(hdrdir)/ruby/internal/intern/dir.h
+generator.o: $(hdrdir)/ruby/internal/intern/enum.h
+generator.o: $(hdrdir)/ruby/internal/intern/enumerator.h
+generator.o: $(hdrdir)/ruby/internal/intern/error.h
+generator.o: $(hdrdir)/ruby/internal/intern/eval.h
+generator.o: $(hdrdir)/ruby/internal/intern/file.h
+generator.o: $(hdrdir)/ruby/internal/intern/hash.h
+generator.o: $(hdrdir)/ruby/internal/intern/io.h
+generator.o: $(hdrdir)/ruby/internal/intern/load.h
+generator.o: $(hdrdir)/ruby/internal/intern/marshal.h
+generator.o: $(hdrdir)/ruby/internal/intern/numeric.h
+generator.o: $(hdrdir)/ruby/internal/intern/object.h
+generator.o: $(hdrdir)/ruby/internal/intern/parse.h
+generator.o: $(hdrdir)/ruby/internal/intern/proc.h
+generator.o: $(hdrdir)/ruby/internal/intern/process.h
+generator.o: $(hdrdir)/ruby/internal/intern/random.h
+generator.o: $(hdrdir)/ruby/internal/intern/range.h
+generator.o: $(hdrdir)/ruby/internal/intern/rational.h
+generator.o: $(hdrdir)/ruby/internal/intern/re.h
+generator.o: $(hdrdir)/ruby/internal/intern/ruby.h
+generator.o: $(hdrdir)/ruby/internal/intern/select.h
+generator.o: $(hdrdir)/ruby/internal/intern/select/largesize.h
+generator.o: $(hdrdir)/ruby/internal/intern/set.h
+generator.o: $(hdrdir)/ruby/internal/intern/signal.h
+generator.o: $(hdrdir)/ruby/internal/intern/sprintf.h
+generator.o: $(hdrdir)/ruby/internal/intern/string.h
+generator.o: $(hdrdir)/ruby/internal/intern/struct.h
+generator.o: $(hdrdir)/ruby/internal/intern/thread.h
+generator.o: $(hdrdir)/ruby/internal/intern/time.h
+generator.o: $(hdrdir)/ruby/internal/intern/variable.h
+generator.o: $(hdrdir)/ruby/internal/intern/vm.h
+generator.o: $(hdrdir)/ruby/internal/interpreter.h
+generator.o: $(hdrdir)/ruby/internal/iterator.h
+generator.o: $(hdrdir)/ruby/internal/memory.h
+generator.o: $(hdrdir)/ruby/internal/method.h
+generator.o: $(hdrdir)/ruby/internal/module.h
+generator.o: $(hdrdir)/ruby/internal/newobj.h
+generator.o: $(hdrdir)/ruby/internal/scan_args.h
+generator.o: $(hdrdir)/ruby/internal/special_consts.h
+generator.o: $(hdrdir)/ruby/internal/static_assert.h
+generator.o: $(hdrdir)/ruby/internal/stdalign.h
+generator.o: $(hdrdir)/ruby/internal/stdbool.h
+generator.o: $(hdrdir)/ruby/internal/stdckdint.h
+generator.o: $(hdrdir)/ruby/internal/symbol.h
+generator.o: $(hdrdir)/ruby/internal/value.h
+generator.o: $(hdrdir)/ruby/internal/value_type.h
+generator.o: $(hdrdir)/ruby/internal/variable.h
+generator.o: $(hdrdir)/ruby/internal/warning_push.h
+generator.o: $(hdrdir)/ruby/internal/xmalloc.h
+generator.o: $(hdrdir)/ruby/missing.h
+generator.o: $(hdrdir)/ruby/onigmo.h
+generator.o: $(hdrdir)/ruby/oniguruma.h
+generator.o: $(hdrdir)/ruby/re.h
+generator.o: $(hdrdir)/ruby/regex.h
+generator.o: $(hdrdir)/ruby/ruby.h
+generator.o: $(hdrdir)/ruby/st.h
+generator.o: $(hdrdir)/ruby/subst.h
+generator.o: $(srcdir)/../fbuffer/fbuffer.h
+generator.o: $(srcdir)/../json.h
+generator.o: $(srcdir)/../simd/simd.h
+generator.o: $(srcdir)/../vendor/fpconv.c
+generator.o: $(srcdir)/../vendor/jeaiii-ltoa.h
+generator.o: generator.c
+# AUTOGENERATED DEPENDENCIES END
diff --git a/ext/json/generator/extconf.rb b/ext/json/generator/extconf.rb
new file mode 100644
index 0000000000..205a887e78
--- /dev/null
+++ b/ext/json/generator/extconf.rb
@@ -0,0 +1,18 @@
+require 'mkmf'
+
+if RUBY_ENGINE == 'truffleruby'
+ # The pure-Ruby generator is faster on TruffleRuby, so skip compiling the generator extension
+ File.write('Makefile', dummy_makefile("").join)
+else
+ append_cflags("-std=c99")
+ have_const("RUBY_TYPED_EMBEDDABLE", "ruby.h") # RUBY_VERSION >= 3.3
+
+ $defs << "-DJSON_GENERATOR"
+ $defs << "-DJSON_DEBUG" if ENV.fetch("JSON_DEBUG", "0") != "0"
+
+ if enable_config('generator-use-simd', default=!ENV["JSON_DISABLE_SIMD"])
+ load __dir__ + "/../simd/conf.rb"
+ end
+
+ create_makefile 'json/ext/generator'
+end
diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c
new file mode 100644
index 0000000000..110b5f6b32
--- /dev/null
+++ b/ext/json/generator/generator.c
@@ -0,0 +1,1997 @@
+#include "../json.h"
+#include "../fbuffer/fbuffer.h"
+#include "../vendor/fpconv.c"
+
+#include <math.h>
+#include <ctype.h>
+
+#include "../simd/simd.h"
+
+/* ruby api and some helpers */
+
+enum duplicate_key_action {
+ JSON_DEPRECATED = 0,
+ JSON_IGNORE,
+ JSON_RAISE,
+};
+
+typedef struct JSON_Generator_StateStruct {
+ VALUE indent;
+ VALUE space;
+ VALUE space_before;
+ VALUE object_nl;
+ VALUE array_nl;
+ VALUE as_json;
+
+ long max_nesting;
+ long depth;
+ long buffer_initial_length;
+
+ enum duplicate_key_action on_duplicate_key;
+
+ bool as_json_single_arg;
+ bool allow_nan;
+ bool ascii_only;
+ bool script_safe;
+ bool strict;
+} JSON_Generator_State;
+
+static VALUE mJSON, cState, cFragment, eGeneratorError, eNestingError, Encoding_UTF_8;
+
+static ID i_to_s, i_to_json, i_new, i_encode;
+static VALUE sym_indent, sym_space, sym_space_before, sym_object_nl, sym_array_nl, sym_max_nesting, sym_allow_nan, sym_allow_duplicate_key,
+ sym_ascii_only, sym_depth, sym_buffer_initial_length, sym_script_safe, sym_escape_slash, sym_strict, sym_as_json;
+
+
+#define GET_STATE_TO(self, state) \
+ TypedData_Get_Struct(self, JSON_Generator_State, &JSON_Generator_State_type, state)
+
+#define GET_STATE(self) \
+ JSON_Generator_State *state; \
+ GET_STATE_TO(self, state)
+
+struct generate_json_data;
+
+typedef void (*generator_func)(FBuffer *buffer, struct generate_json_data *data, VALUE obj);
+
+struct generate_json_data {
+ FBuffer *buffer;
+ VALUE vstate;
+ JSON_Generator_State *state;
+ VALUE obj;
+ generator_func func;
+ long depth;
+};
+
+static SIMD_Implementation simd_impl;
+
+static VALUE cState_from_state_s(VALUE self, VALUE opts);
+static VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func, VALUE io);
+static void generate_json(FBuffer *buffer, struct generate_json_data *data, VALUE obj);
+static void generate_json_object(FBuffer *buffer, struct generate_json_data *data, VALUE obj);
+static void generate_json_array(FBuffer *buffer, struct generate_json_data *data, VALUE obj);
+static void generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj);
+static void generate_json_null(FBuffer *buffer, struct generate_json_data *data, VALUE obj);
+static void generate_json_false(FBuffer *buffer, struct generate_json_data *data, VALUE obj);
+static void generate_json_true(FBuffer *buffer, struct generate_json_data *data, VALUE obj);
+static void generate_json_fixnum(FBuffer *buffer, struct generate_json_data *data, VALUE obj);
+static void generate_json_bignum(FBuffer *buffer, struct generate_json_data *data, VALUE obj);
+static void generate_json_float(FBuffer *buffer, struct generate_json_data *data, VALUE obj);
+static void generate_json_fragment(FBuffer *buffer, struct generate_json_data *data, VALUE obj);
+
+static int usascii_encindex, utf8_encindex, binary_encindex;
+
+NORETURN(static void) raise_generator_error_str(VALUE invalid_object, VALUE str)
+{
+ rb_enc_associate_index(str, utf8_encindex);
+ VALUE exc = rb_exc_new_str(eGeneratorError, str);
+ rb_ivar_set(exc, rb_intern("@invalid_object"), invalid_object);
+ rb_exc_raise(exc);
+}
+
+#ifdef RBIMPL_ATTR_FORMAT
+RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 2, 3)
+#endif
+NORETURN(static void) raise_generator_error(VALUE invalid_object, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ VALUE str = rb_vsprintf(fmt, args);
+ va_end(args);
+ raise_generator_error_str(invalid_object, str);
+}
+
+// 0 - single byte char that don't need to be escaped.
+// (x | 8) - char that needs to be escaped.
+static const unsigned char CHAR_LENGTH_MASK = 7;
+static const unsigned char ESCAPE_MASK = 8;
+
+typedef struct _search_state {
+ const char *ptr;
+ const char *end;
+ const char *cursor;
+ FBuffer *buffer;
+
+#ifdef HAVE_SIMD
+ const char *chunk_base;
+ const char *chunk_end;
+ bool has_matches;
+
+#if defined(HAVE_SIMD_NEON)
+ uint64_t matches_mask;
+#elif defined(HAVE_SIMD_SSE2)
+ int matches_mask;
+#else
+#error "Unknown SIMD Implementation."
+#endif /* HAVE_SIMD_NEON */
+#endif /* HAVE_SIMD */
+} search_state;
+
+ALWAYS_INLINE(static) void search_flush(search_state *search)
+{
+ // Do not remove this conditional without profiling, specifically escape-heavy text.
+ // escape_UTF8_char_basic will advance search->ptr and search->cursor (effectively a search_flush).
+ // For back-to-back characters that need to be escaped, specifically for the SIMD code paths, this method
+ // will be called just before calling escape_UTF8_char_basic. There will be no characters to append for the
+ // consecutive characters that need to be escaped. While the fbuffer_append is a no-op if
+ // nothing needs to be flushed, we can save a few memory references with this conditional.
+ if (search->ptr > search->cursor) {
+ fbuffer_append(search->buffer, search->cursor, search->ptr - search->cursor);
+ search->cursor = search->ptr;
+ }
+}
+
+static const unsigned char escape_table_basic[256] = {
+ // ASCII Control Characters
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ // ASCII Characters
+ 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // '"'
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, // '\\'
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static inline unsigned char search_escape_basic(search_state *search)
+{
+ while (search->ptr < search->end) {
+ if (RB_UNLIKELY(escape_table_basic[(const unsigned char)*search->ptr])) {
+ search_flush(search);
+ return 1;
+ } else {
+ search->ptr++;
+ }
+ }
+ search_flush(search);
+ return 0;
+}
+
+ALWAYS_INLINE(static) void escape_UTF8_char_basic(search_state *search)
+{
+ const unsigned char ch = (unsigned char)*search->ptr;
+ switch (ch) {
+ case '"': fbuffer_append(search->buffer, "\\\"", 2); break;
+ case '\\': fbuffer_append(search->buffer, "\\\\", 2); break;
+ case '/': fbuffer_append(search->buffer, "\\/", 2); break;
+ case '\b': fbuffer_append(search->buffer, "\\b", 2); break;
+ case '\f': fbuffer_append(search->buffer, "\\f", 2); break;
+ case '\n': fbuffer_append(search->buffer, "\\n", 2); break;
+ case '\r': fbuffer_append(search->buffer, "\\r", 2); break;
+ case '\t': fbuffer_append(search->buffer, "\\t", 2); break;
+ default: {
+ const char *hexdig = "0123456789abcdef";
+ char scratch[6] = { '\\', 'u', '0', '0', 0, 0 };
+ scratch[4] = hexdig[(ch >> 4) & 0xf];
+ scratch[5] = hexdig[ch & 0xf];
+ fbuffer_append(search->buffer, scratch, 6);
+ break;
+ }
+ }
+ search->ptr++;
+ search->cursor = search->ptr;
+}
+
+/* Converts in_string to a JSON string (without the wrapping '"'
+ * characters) in FBuffer out_buffer.
+ *
+ * Character are JSON-escaped according to:
+ *
+ * - Always: ASCII control characters (0x00-0x1F), dquote, and
+ * backslash.
+ *
+ * - If out_ascii_only: non-ASCII characters (>0x7F)
+ *
+ * - If script_safe: forwardslash (/), line separator (U+2028), and
+ * paragraph separator (U+2029)
+ *
+ * Everything else (should be UTF-8) is just passed through and
+ * appended to the result.
+ */
+
+
+#if defined(HAVE_SIMD_NEON)
+static inline unsigned char search_escape_basic_neon(search_state *search);
+#elif defined(HAVE_SIMD_SSE2)
+static inline unsigned char search_escape_basic_sse2(search_state *search);
+#endif
+
+static inline unsigned char search_escape_basic(search_state *search);
+
+static inline void convert_UTF8_to_JSON(search_state *search)
+{
+#ifdef HAVE_SIMD
+#if defined(HAVE_SIMD_NEON)
+ while (search_escape_basic_neon(search)) {
+ escape_UTF8_char_basic(search);
+ }
+#elif defined(HAVE_SIMD_SSE2)
+ if (simd_impl == SIMD_SSE2) {
+ while (search_escape_basic_sse2(search)) {
+ escape_UTF8_char_basic(search);
+ }
+ return;
+ }
+ while (search_escape_basic(search)) {
+ escape_UTF8_char_basic(search);
+ }
+#endif
+#else
+ while (search_escape_basic(search)) {
+ escape_UTF8_char_basic(search);
+ }
+#endif /* HAVE_SIMD */
+}
+
+static inline void escape_UTF8_char(search_state *search, unsigned char ch_len)
+{
+ const unsigned char ch = (unsigned char)*search->ptr;
+ switch (ch_len) {
+ case 1: {
+ switch (ch) {
+ case '"': fbuffer_append(search->buffer, "\\\"", 2); break;
+ case '\\': fbuffer_append(search->buffer, "\\\\", 2); break;
+ case '/': fbuffer_append(search->buffer, "\\/", 2); break;
+ case '\b': fbuffer_append(search->buffer, "\\b", 2); break;
+ case '\f': fbuffer_append(search->buffer, "\\f", 2); break;
+ case '\n': fbuffer_append(search->buffer, "\\n", 2); break;
+ case '\r': fbuffer_append(search->buffer, "\\r", 2); break;
+ case '\t': fbuffer_append(search->buffer, "\\t", 2); break;
+ default: {
+ const char *hexdig = "0123456789abcdef";
+ char scratch[6] = { '\\', 'u', '0', '0', 0, 0 };
+ scratch[4] = hexdig[(ch >> 4) & 0xf];
+ scratch[5] = hexdig[ch & 0xf];
+ fbuffer_append(search->buffer, scratch, 6);
+ break;
+ }
+ }
+ break;
+ }
+ case 3: {
+ if (search->ptr[2] & 1) {
+ fbuffer_append(search->buffer, "\\u2029", 6);
+ } else {
+ fbuffer_append(search->buffer, "\\u2028", 6);
+ }
+ break;
+ }
+ }
+ search->cursor = (search->ptr += ch_len);
+}
+
+#ifdef HAVE_SIMD
+
+ALWAYS_INLINE(static) char *copy_remaining_bytes(search_state *search, unsigned long vec_len, unsigned long len)
+{
+ RBIMPL_ASSERT_OR_ASSUME(len < vec_len);
+
+ // Flush the buffer so everything up until the last 'len' characters are unflushed.
+ search_flush(search);
+
+ FBuffer *buf = search->buffer;
+ fbuffer_inc_capa(buf, vec_len);
+
+ char *s = (buf->ptr + buf->len);
+
+ // Pad the buffer with dummy characters that won't need escaping.
+ // This seem wasteful at first sight, but memset of vector length is very fast.
+ // This is a space as it can be directly represented as an immediate on AArch64.
+ memset(s, ' ', vec_len);
+
+ // Optimistically copy the remaining 'len' characters to the output FBuffer. If there are no characters
+ // to escape, then everything ends up in the correct spot. Otherwise it was convenient temporary storage.
+ if (vec_len == 16) {
+ RBIMPL_ASSERT_OR_ASSUME(len >= SIMD_MINIMUM_THRESHOLD);
+ json_fast_memcpy16(s, search->ptr, len);
+ } else {
+ MEMCPY(s, search->ptr, char, len);
+ }
+
+ return s;
+}
+
+#ifdef HAVE_SIMD_NEON
+
+ALWAYS_INLINE(static) unsigned char neon_next_match(search_state *search)
+{
+ uint64_t mask = search->matches_mask;
+ uint32_t index = trailing_zeros64(mask) >> 2;
+
+ // It is assumed escape_UTF8_char_basic will only ever increase search->ptr by at most one character.
+ // If we want to use a similar approach for full escaping we'll need to ensure:
+ // search->chunk_base + index >= search->ptr
+ // However, since we know escape_UTF8_char_basic only increases search->ptr by one, if the next match
+ // is one byte after the previous match then:
+ // search->chunk_base + index == search->ptr
+ search->ptr = search->chunk_base + index;
+ mask &= mask - 1;
+ search->matches_mask = mask;
+ search_flush(search);
+ return 1;
+}
+
+static inline unsigned char search_escape_basic_neon(search_state *search)
+{
+ if (RB_UNLIKELY(search->has_matches)) {
+ // There are more matches if search->matches_mask > 0.
+ if (search->matches_mask > 0) {
+ return neon_next_match(search);
+ } else {
+ // neon_next_match will only advance search->ptr up to the last matching character.
+ // Skip over any characters in the last chunk that occur after the last match.
+ search->has_matches = false;
+ search->ptr = search->chunk_end;
+ }
+ }
+
+ /*
+ * The code below implements an SIMD-based algorithm to determine if N bytes at a time
+ * need to be escaped.
+ *
+ * Assume the ptr = "Te\sting!" (the double quotes are included in the string)
+ *
+ * The explanation will be limited to the first 8 bytes of the string for simplicity. However
+ * the vector insructions may work on larger vectors.
+ *
+ * First, we load three constants 'lower_bound', 'backslash' and 'dblquote" in vector registers.
+ *
+ * lower_bound: [20 20 20 20 20 20 20 20]
+ * backslash: [5C 5C 5C 5C 5C 5C 5C 5C]
+ * dblquote: [22 22 22 22 22 22 22 22]
+ *
+ * Next we load the first chunk of the ptr:
+ * [22 54 65 5C 73 74 69 6E] (" T e \ s t i n)
+ *
+ * First we check if any byte in chunk is less than 32 (0x20). This returns the following vector
+ * as no bytes are less than 32 (0x20):
+ * [0 0 0 0 0 0 0 0]
+ *
+ * Next, we check if any byte in chunk is equal to a backslash:
+ * [0 0 0 FF 0 0 0 0]
+ *
+ * Finally we check if any byte in chunk is equal to a double quote:
+ * [FF 0 0 0 0 0 0 0]
+ *
+ * Now we have three vectors where each byte indicates if the corresponding byte in chunk
+ * needs to be escaped. We combine these vectors with a series of logical OR instructions.
+ * This is the needs_escape vector and it is equal to:
+ * [FF 0 0 FF 0 0 0 0]
+ *
+ * Next we compute the bitwise AND between each byte and 0x1 and compute the horizontal sum of
+ * the values in the vector. This computes how many bytes need to be escaped within this chunk.
+ *
+ * Finally we compute a mask that indicates which bytes need to be escaped. If the mask is 0 then,
+ * no bytes need to be escaped and we can continue to the next chunk. If the mask is not 0 then we
+ * have at least one byte that needs to be escaped.
+ */
+
+ if (string_scan_simd_neon(&search->ptr, search->end, &search->matches_mask)) {
+ search->has_matches = true;
+ search->chunk_base = search->ptr;
+ search->chunk_end = search->ptr + sizeof(uint8x16_t);
+ return neon_next_match(search);
+ }
+
+ // There are fewer than 16 bytes left.
+ unsigned long remaining = (search->end - search->ptr);
+ if (remaining >= SIMD_MINIMUM_THRESHOLD) {
+ char *s = copy_remaining_bytes(search, sizeof(uint8x16_t), remaining);
+
+ uint64_t mask = compute_chunk_mask_neon(s);
+
+ if (!mask) {
+ // Nothing to escape, ensure search_flush doesn't do anything by setting
+ // search->cursor to search->ptr.
+ fbuffer_consumed(search->buffer, remaining);
+ search->ptr = search->end;
+ search->cursor = search->end;
+ return 0;
+ }
+
+ search->matches_mask = mask;
+ search->has_matches = true;
+ search->chunk_end = search->end;
+ search->chunk_base = search->ptr;
+ return neon_next_match(search);
+ }
+
+ if (search->ptr < search->end) {
+ return search_escape_basic(search);
+ }
+
+ search_flush(search);
+ return 0;
+}
+#endif /* HAVE_SIMD_NEON */
+
+#ifdef HAVE_SIMD_SSE2
+
+ALWAYS_INLINE(static) unsigned char sse2_next_match(search_state *search)
+{
+ int mask = search->matches_mask;
+ int index = trailing_zeros(mask);
+
+ // It is assumed escape_UTF8_char_basic will only ever increase search->ptr by at most one character.
+ // If we want to use a similar approach for full escaping we'll need to ensure:
+ // search->chunk_base + index >= search->ptr
+ // However, since we know escape_UTF8_char_basic only increases search->ptr by one, if the next match
+ // is one byte after the previous match then:
+ // search->chunk_base + index == search->ptr
+ search->ptr = search->chunk_base + index;
+ mask &= mask - 1;
+ search->matches_mask = mask;
+ search_flush(search);
+ return 1;
+}
+
+#if defined(__clang__) || defined(__GNUC__)
+#define TARGET_SSE2 __attribute__((target("sse2")))
+#else
+#define TARGET_SSE2
+#endif
+
+ALWAYS_INLINE(static) TARGET_SSE2 unsigned char search_escape_basic_sse2(search_state *search)
+{
+ if (RB_UNLIKELY(search->has_matches)) {
+ // There are more matches if search->matches_mask > 0.
+ if (search->matches_mask > 0) {
+ return sse2_next_match(search);
+ } else {
+ // sse2_next_match will only advance search->ptr up to the last matching character.
+ // Skip over any characters in the last chunk that occur after the last match.
+ search->has_matches = false;
+ if (RB_UNLIKELY(search->chunk_base + sizeof(__m128i) >= search->end)) {
+ search->ptr = search->end;
+ } else {
+ search->ptr = search->chunk_base + sizeof(__m128i);
+ }
+ }
+ }
+
+ if (string_scan_simd_sse2(&search->ptr, search->end, &search->matches_mask)) {
+ search->has_matches = true;
+ search->chunk_base = search->ptr;
+ search->chunk_end = search->ptr + sizeof(__m128i);
+ return sse2_next_match(search);
+ }
+
+ // There are fewer than 16 bytes left.
+ unsigned long remaining = (search->end - search->ptr);
+ if (remaining >= SIMD_MINIMUM_THRESHOLD) {
+ char *s = copy_remaining_bytes(search, sizeof(__m128i), remaining);
+
+ int needs_escape_mask = compute_chunk_mask_sse2(s);
+
+ if (needs_escape_mask == 0) {
+ // Nothing to escape, ensure search_flush doesn't do anything by setting
+ // search->cursor to search->ptr.
+ fbuffer_consumed(search->buffer, remaining);
+ search->ptr = search->end;
+ search->cursor = search->end;
+ return 0;
+ }
+
+ search->has_matches = true;
+ search->matches_mask = needs_escape_mask;
+ search->chunk_base = search->ptr;
+ return sse2_next_match(search);
+ }
+
+ if (search->ptr < search->end) {
+ return search_escape_basic(search);
+ }
+
+ search_flush(search);
+ return 0;
+}
+
+#endif /* HAVE_SIMD_SSE2 */
+
+#endif /* HAVE_SIMD */
+
+static const unsigned char script_safe_escape_table[256] = {
+ // ASCII Control Characters
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ // ASCII Characters
+ 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, // '"' and '/'
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, // '\\'
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ // Continuation byte
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ // First byte of a 2-byte code point
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ // First byte of a 3-byte code point
+ 3, 3,11, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0xE2 is the start of \u2028 and \u2029
+ //First byte of a 4+ byte code point
+ 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 9, 9,
+};
+
+static inline unsigned char search_script_safe_escape(search_state *search)
+{
+ while (search->ptr < search->end) {
+ unsigned char ch = (unsigned char)*search->ptr;
+ unsigned char ch_len = script_safe_escape_table[ch];
+
+ if (RB_UNLIKELY(ch_len)) {
+ if (ch_len & ESCAPE_MASK) {
+ if (RB_UNLIKELY(ch_len == 11)) {
+ const unsigned char *uptr = (const unsigned char *)search->ptr;
+ if (!(uptr[1] == 0x80 && (uptr[2] >> 1) == 0x54)) {
+ search->ptr += 3;
+ continue;
+ }
+ }
+ search_flush(search);
+ return ch_len & CHAR_LENGTH_MASK;
+ } else {
+ search->ptr += ch_len;
+ }
+ } else {
+ search->ptr++;
+ }
+ }
+ search_flush(search);
+ return 0;
+}
+
+static void convert_UTF8_to_script_safe_JSON(search_state *search)
+{
+ unsigned char ch_len;
+ while ((ch_len = search_script_safe_escape(search))) {
+ escape_UTF8_char(search, ch_len);
+ }
+}
+
+static const unsigned char ascii_only_escape_table[256] = {
+ // ASCII Control Characters
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ // ASCII Characters
+ 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // '"'
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, // '\\'
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ // Continuation byte
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ // First byte of a 2-byte code point
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ // First byte of a 3-byte code point
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ //First byte of a 4+ byte code point
+ 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 9, 9,
+};
+
+static inline unsigned char search_ascii_only_escape(search_state *search, const unsigned char escape_table[256])
+{
+ while (search->ptr < search->end) {
+ unsigned char ch = (unsigned char)*search->ptr;
+ unsigned char ch_len = escape_table[ch];
+
+ if (RB_UNLIKELY(ch_len)) {
+ search_flush(search);
+ return ch_len & CHAR_LENGTH_MASK;
+ } else {
+ search->ptr++;
+ }
+ }
+ search_flush(search);
+ return 0;
+}
+
+static inline void full_escape_UTF8_char(search_state *search, unsigned char ch_len)
+{
+ const unsigned char ch = (unsigned char)*search->ptr;
+ switch (ch_len) {
+ case 1: {
+ switch (ch) {
+ case '"': fbuffer_append(search->buffer, "\\\"", 2); break;
+ case '\\': fbuffer_append(search->buffer, "\\\\", 2); break;
+ case '/': fbuffer_append(search->buffer, "\\/", 2); break;
+ case '\b': fbuffer_append(search->buffer, "\\b", 2); break;
+ case '\f': fbuffer_append(search->buffer, "\\f", 2); break;
+ case '\n': fbuffer_append(search->buffer, "\\n", 2); break;
+ case '\r': fbuffer_append(search->buffer, "\\r", 2); break;
+ case '\t': fbuffer_append(search->buffer, "\\t", 2); break;
+ default: {
+ const char *hexdig = "0123456789abcdef";
+ char scratch[6] = { '\\', 'u', '0', '0', 0, 0 };
+ scratch[4] = hexdig[(ch >> 4) & 0xf];
+ scratch[5] = hexdig[ch & 0xf];
+ fbuffer_append(search->buffer, scratch, 6);
+ break;
+ }
+ }
+ break;
+ }
+ default: {
+ const char *hexdig = "0123456789abcdef";
+ char scratch[12] = { '\\', 'u', 0, 0, 0, 0, '\\', 'u' };
+
+ uint32_t wchar = 0;
+
+ switch (ch_len) {
+ case 2:
+ wchar = ch & 0x1F;
+ break;
+ case 3:
+ wchar = ch & 0x0F;
+ break;
+ case 4:
+ wchar = ch & 0x07;
+ break;
+ }
+
+ for (short i = 1; i < ch_len; i++) {
+ wchar = (wchar << 6) | (search->ptr[i] & 0x3F);
+ }
+
+ if (wchar <= 0xFFFF) {
+ scratch[2] = hexdig[wchar >> 12];
+ scratch[3] = hexdig[(wchar >> 8) & 0xf];
+ scratch[4] = hexdig[(wchar >> 4) & 0xf];
+ scratch[5] = hexdig[wchar & 0xf];
+ fbuffer_append(search->buffer, scratch, 6);
+ } else {
+ uint16_t hi, lo;
+ wchar -= 0x10000;
+ hi = 0xD800 + (uint16_t)(wchar >> 10);
+ lo = 0xDC00 + (uint16_t)(wchar & 0x3FF);
+
+ scratch[2] = hexdig[hi >> 12];
+ scratch[3] = hexdig[(hi >> 8) & 0xf];
+ scratch[4] = hexdig[(hi >> 4) & 0xf];
+ scratch[5] = hexdig[hi & 0xf];
+
+ scratch[8] = hexdig[lo >> 12];
+ scratch[9] = hexdig[(lo >> 8) & 0xf];
+ scratch[10] = hexdig[(lo >> 4) & 0xf];
+ scratch[11] = hexdig[lo & 0xf];
+
+ fbuffer_append(search->buffer, scratch, 12);
+ }
+
+ break;
+ }
+ }
+ search->cursor = (search->ptr += ch_len);
+}
+
+static void convert_UTF8_to_ASCII_only_JSON(search_state *search, const unsigned char escape_table[256])
+{
+ unsigned char ch_len;
+ while ((ch_len = search_ascii_only_escape(search, escape_table))) {
+ full_escape_UTF8_char(search, ch_len);
+ }
+}
+
+static void State_mark(void *ptr)
+{
+ JSON_Generator_State *state = ptr;
+ rb_gc_mark_movable(state->indent);
+ rb_gc_mark_movable(state->space);
+ rb_gc_mark_movable(state->space_before);
+ rb_gc_mark_movable(state->object_nl);
+ rb_gc_mark_movable(state->array_nl);
+ rb_gc_mark_movable(state->as_json);
+}
+
+static void State_compact(void *ptr)
+{
+ JSON_Generator_State *state = ptr;
+ state->indent = rb_gc_location(state->indent);
+ state->space = rb_gc_location(state->space);
+ state->space_before = rb_gc_location(state->space_before);
+ state->object_nl = rb_gc_location(state->object_nl);
+ state->array_nl = rb_gc_location(state->array_nl);
+ state->as_json = rb_gc_location(state->as_json);
+}
+
+static size_t State_memsize(const void *ptr)
+{
+ return sizeof(JSON_Generator_State);
+}
+
+static const rb_data_type_t JSON_Generator_State_type = {
+ .wrap_struct_name = "JSON/Generator/State",
+ .function = {
+ .dmark = State_mark,
+ .dfree = RUBY_DEFAULT_FREE,
+ .dsize = State_memsize,
+ .dcompact = State_compact,
+ },
+ .flags = RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_EMBEDDABLE,
+};
+
+static void state_init(JSON_Generator_State *state)
+{
+ state->max_nesting = 100;
+ state->buffer_initial_length = FBUFFER_INITIAL_LENGTH_DEFAULT;
+}
+
+static VALUE cState_s_allocate(VALUE klass)
+{
+ JSON_Generator_State *state;
+ VALUE obj = TypedData_Make_Struct(klass, JSON_Generator_State, &JSON_Generator_State_type, state);
+ state_init(state);
+ return obj;
+}
+
+static void vstate_spill(struct generate_json_data *data)
+{
+ VALUE vstate = cState_s_allocate(cState);
+ GET_STATE(vstate);
+ MEMCPY(state, data->state, JSON_Generator_State, 1);
+ data->state = state;
+ data->vstate = vstate;
+ RB_OBJ_WRITTEN(vstate, Qundef, state->indent);
+ RB_OBJ_WRITTEN(vstate, Qundef, state->space);
+ RB_OBJ_WRITTEN(vstate, Qundef, state->space_before);
+ RB_OBJ_WRITTEN(vstate, Qundef, state->object_nl);
+ RB_OBJ_WRITTEN(vstate, Qundef, state->array_nl);
+ RB_OBJ_WRITTEN(vstate, Qundef, state->as_json);
+}
+
+static inline VALUE json_call_to_json(struct generate_json_data *data, VALUE obj)
+{
+ if (RB_UNLIKELY(!data->vstate)) {
+ vstate_spill(data);
+ }
+ GET_STATE(data->vstate);
+ state->depth = data->depth;
+ VALUE tmp = rb_funcall(obj, i_to_json, 1, data->vstate);
+ // no need to restore state->depth, vstate is just a temporary State
+ return tmp;
+}
+
+static VALUE
+json_call_as_json(JSON_Generator_State *state, VALUE object, VALUE is_key)
+{
+ VALUE proc_args[2] = {object, is_key};
+ return rb_proc_call_with_block(state->as_json, 2, proc_args, Qnil);
+}
+
+static VALUE
+convert_string_subclass(VALUE key)
+{
+ VALUE key_to_s = rb_funcall(key, i_to_s, 0);
+
+ if (RB_UNLIKELY(!RB_TYPE_P(key_to_s, T_STRING))) {
+ VALUE cname = rb_obj_class(key);
+ rb_raise(rb_eTypeError,
+ "can't convert %"PRIsVALUE" to %s (%"PRIsVALUE"#%s gives %"PRIsVALUE")",
+ cname, "String", cname, "to_s", rb_obj_class(key_to_s));
+ }
+
+ return key_to_s;
+}
+
+static bool enc_utf8_compatible_p(int enc_idx)
+{
+ if (enc_idx == usascii_encindex) return true;
+ if (enc_idx == utf8_encindex) return true;
+ return false;
+}
+
+static VALUE encode_json_string_try(VALUE str)
+{
+ return rb_funcall(str, i_encode, 1, Encoding_UTF_8);
+}
+
+static VALUE encode_json_string_rescue(VALUE str, VALUE exception)
+{
+ raise_generator_error_str(str, rb_funcall(exception, rb_intern("message"), 0));
+ return Qundef;
+}
+
+static inline int json_str_coderange(VALUE str) {
+ int coderange = RB_ENC_CODERANGE(str);
+ if (coderange == RUBY_ENC_CODERANGE_UNKNOWN) {
+ coderange = rb_enc_str_coderange(str);
+ }
+ return coderange;
+}
+
+static inline bool valid_json_string_p(VALUE str)
+{
+ int coderange = json_str_coderange(str);
+
+ if (RB_LIKELY(coderange == ENC_CODERANGE_7BIT)) {
+ return true;
+ }
+
+ if (RB_LIKELY(coderange == ENC_CODERANGE_VALID)) {
+ return enc_utf8_compatible_p(RB_ENCODING_GET_INLINED(str));
+ }
+
+ return false;
+}
+
+NOINLINE(static) VALUE convert_invalid_encoding(struct generate_json_data *data, VALUE str, bool as_json_called, bool is_key)
+{
+ if (!as_json_called && data->state->strict && RTEST(data->state->as_json)) {
+ VALUE coerced_str = json_call_as_json(data->state, str, Qfalse);
+ if (coerced_str != str) {
+ if (RB_TYPE_P(coerced_str, T_STRING)) {
+ if (!valid_json_string_p(coerced_str)) {
+ raise_generator_error(str, "source sequence is illegal/malformed utf-8");
+ }
+ } else {
+ // as_json could return another type than T_STRING
+ if (is_key) {
+ raise_generator_error(coerced_str, "%"PRIsVALUE" not allowed as object key in JSON", CLASS_OF(coerced_str));
+ }
+ }
+
+ return coerced_str;
+ }
+ }
+
+ if (RB_ENCODING_GET_INLINED(str) == binary_encindex) {
+ VALUE utf8_string = rb_enc_associate_index(rb_str_dup(str), utf8_encindex);
+ switch (rb_enc_str_coderange(utf8_string)) {
+ case ENC_CODERANGE_7BIT:
+ return utf8_string;
+ case ENC_CODERANGE_VALID:
+ // For historical reason, we silently reinterpret binary strings as UTF-8 if it would work.
+ // TODO: Raise in 3.0.0
+ rb_warn("JSON.generate: UTF-8 string passed as BINARY, this will raise an encoding error in json 3.0");
+ return utf8_string;
+ break;
+ }
+ }
+
+ return rb_rescue(encode_json_string_try, str, encode_json_string_rescue, str);
+}
+
+ALWAYS_INLINE(static) VALUE ensure_valid_encoding(struct generate_json_data *data, VALUE str, bool as_json_called, bool is_key)
+{
+ if (RB_LIKELY(valid_json_string_p(str))) {
+ return str;
+ }
+ else {
+ return convert_invalid_encoding(data, str, as_json_called, is_key);
+ }
+}
+
+static void raw_generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
+{
+ fbuffer_append_char(buffer, '"');
+
+ long len;
+ search_state search;
+ search.buffer = buffer;
+ RSTRING_GETMEM(obj, search.ptr, len);
+ search.cursor = search.ptr;
+ search.end = search.ptr + len;
+
+#ifdef HAVE_SIMD
+ search.matches_mask = 0;
+ search.has_matches = false;
+ search.chunk_base = NULL;
+ search.chunk_end = NULL;
+#endif /* HAVE_SIMD */
+
+ switch (json_str_coderange(obj)) {
+ case ENC_CODERANGE_7BIT:
+ case ENC_CODERANGE_VALID:
+ if (RB_UNLIKELY(data->state->ascii_only)) {
+ convert_UTF8_to_ASCII_only_JSON(&search, data->state->script_safe ? script_safe_escape_table : ascii_only_escape_table);
+ } else if (RB_UNLIKELY(data->state->script_safe)) {
+ convert_UTF8_to_script_safe_JSON(&search);
+ } else {
+ convert_UTF8_to_JSON(&search);
+ }
+ break;
+ default:
+ raise_generator_error(obj, "source sequence is illegal/malformed utf-8");
+ break;
+ }
+ fbuffer_append_char(buffer, '"');
+}
+
+static void generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
+{
+ obj = ensure_valid_encoding(data, obj, false, false);
+ raw_generate_json_string(buffer, data, obj);
+}
+
+struct hash_foreach_arg {
+ VALUE hash;
+ struct generate_json_data *data;
+ int first_key_type;
+ bool first;
+ bool mixed_keys_encountered;
+};
+
+NOINLINE(static) void
+json_inspect_hash_with_mixed_keys(struct hash_foreach_arg *arg)
+{
+ if (arg->mixed_keys_encountered) {
+ return;
+ }
+ arg->mixed_keys_encountered = true;
+
+ JSON_Generator_State *state = arg->data->state;
+ if (state->on_duplicate_key != JSON_IGNORE) {
+ VALUE do_raise = state->on_duplicate_key == JSON_RAISE ? Qtrue : Qfalse;
+ rb_funcall(mJSON, rb_intern("on_mixed_keys_hash"), 2, arg->hash, do_raise);
+ }
+}
+
+static int
+json_object_i(VALUE key, VALUE val, VALUE _arg)
+{
+ struct hash_foreach_arg *arg = (struct hash_foreach_arg *)_arg;
+ struct generate_json_data *data = arg->data;
+
+ FBuffer *buffer = data->buffer;
+ JSON_Generator_State *state = data->state;
+
+ long depth = data->depth;
+ int key_type = rb_type(key);
+
+ if (arg->first) {
+ arg->first = false;
+ arg->first_key_type = key_type;
+ }
+ else {
+ fbuffer_append_char(buffer, ',');
+ }
+
+ if (RB_UNLIKELY(data->state->object_nl)) {
+ fbuffer_append_str(buffer, data->state->object_nl);
+ }
+ if (RB_UNLIKELY(data->state->indent)) {
+ fbuffer_append_str_repeat(buffer, data->state->indent, depth);
+ }
+
+ VALUE key_to_s;
+ bool as_json_called = false;
+
+ start:
+ switch (key_type) {
+ case T_STRING:
+ if (RB_UNLIKELY(arg->first_key_type != T_STRING)) {
+ json_inspect_hash_with_mixed_keys(arg);
+ }
+
+ if (RB_LIKELY(RBASIC_CLASS(key) == rb_cString)) {
+ key_to_s = key;
+ } else {
+ key_to_s = convert_string_subclass(key);
+ }
+ break;
+ case T_SYMBOL:
+ if (RB_UNLIKELY(arg->first_key_type != T_SYMBOL)) {
+ json_inspect_hash_with_mixed_keys(arg);
+ }
+
+ key_to_s = rb_sym2str(key);
+ break;
+ default:
+ if (data->state->strict) {
+ if (RTEST(data->state->as_json) && !as_json_called) {
+ key = json_call_as_json(data->state, key, Qtrue);
+ key_type = rb_type(key);
+ as_json_called = true;
+ goto start;
+ } else {
+ raise_generator_error(key, "%"PRIsVALUE" not allowed as object key in JSON", CLASS_OF(key));
+ }
+ }
+ key_to_s = rb_convert_type(key, T_STRING, "String", "to_s");
+ break;
+ }
+
+ key_to_s = ensure_valid_encoding(data, key_to_s, as_json_called, true);
+
+ if (RB_LIKELY(RBASIC_CLASS(key_to_s) == rb_cString)) {
+ raw_generate_json_string(buffer, data, key_to_s);
+ } else {
+ generate_json(buffer, data, key_to_s);
+ }
+ if (RB_UNLIKELY(state->space_before)) fbuffer_append_str(buffer, data->state->space_before);
+ fbuffer_append_char(buffer, ':');
+ if (RB_UNLIKELY(state->space)) fbuffer_append_str(buffer, data->state->space);
+ generate_json(buffer, data, val);
+
+ return ST_CONTINUE;
+}
+
+static inline long increase_depth(struct generate_json_data *data)
+{
+ JSON_Generator_State *state = data->state;
+ long depth = ++data->depth;
+ if (RB_UNLIKELY(depth > state->max_nesting && state->max_nesting)) {
+ rb_raise(eNestingError, "nesting of %ld is too deep. Did you try to serialize objects with circular references?", --data->depth);
+ }
+ return depth;
+}
+
+static void generate_json_object(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
+{
+ long depth = increase_depth(data);
+
+ if (RHASH_SIZE(obj) == 0) {
+ fbuffer_append(buffer, "{}", 2);
+ --data->depth;
+ return;
+ }
+
+ fbuffer_append_char(buffer, '{');
+
+ struct hash_foreach_arg arg = {
+ .hash = obj,
+ .data = data,
+ .first = true,
+ };
+ rb_hash_foreach(obj, json_object_i, (VALUE)&arg);
+
+ depth = --data->depth;
+ if (RB_UNLIKELY(data->state->object_nl)) {
+ fbuffer_append_str(buffer, data->state->object_nl);
+ if (RB_UNLIKELY(data->state->indent)) {
+ fbuffer_append_str_repeat(buffer, data->state->indent, depth);
+ }
+ }
+ fbuffer_append_char(buffer, '}');
+}
+
+static void generate_json_array(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
+{
+ long depth = increase_depth(data);
+
+ if (RARRAY_LEN(obj) == 0) {
+ fbuffer_append(buffer, "[]", 2);
+ --data->depth;
+ return;
+ }
+
+ fbuffer_append_char(buffer, '[');
+ if (RB_UNLIKELY(data->state->array_nl)) fbuffer_append_str(buffer, data->state->array_nl);
+ for (int i = 0; i < RARRAY_LEN(obj); i++) {
+ if (i > 0) {
+ fbuffer_append_char(buffer, ',');
+ if (RB_UNLIKELY(data->state->array_nl)) fbuffer_append_str(buffer, data->state->array_nl);
+ }
+ if (RB_UNLIKELY(data->state->indent)) {
+ fbuffer_append_str_repeat(buffer, data->state->indent, depth);
+ }
+ generate_json(buffer, data, RARRAY_AREF(obj, i));
+ }
+ data->depth = --depth;
+ if (RB_UNLIKELY(data->state->array_nl)) {
+ fbuffer_append_str(buffer, data->state->array_nl);
+ if (RB_UNLIKELY(data->state->indent)) {
+ fbuffer_append_str_repeat(buffer, data->state->indent, depth);
+ }
+ }
+ fbuffer_append_char(buffer, ']');
+}
+
+static void generate_json_fallback(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
+{
+ VALUE tmp;
+ if (rb_respond_to(obj, i_to_json)) {
+ tmp = json_call_to_json(data, obj);
+ Check_Type(tmp, T_STRING);
+ fbuffer_append_str(buffer, tmp);
+ } else {
+ tmp = rb_funcall(obj, i_to_s, 0);
+ Check_Type(tmp, T_STRING);
+ generate_json_string(buffer, data, tmp);
+ }
+}
+
+static inline void generate_json_symbol(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
+{
+ if (data->state->strict) {
+ generate_json_string(buffer, data, rb_sym2str(obj));
+ } else {
+ generate_json_fallback(buffer, data, obj);
+ }
+}
+
+static void generate_json_null(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
+{
+ fbuffer_append(buffer, "null", 4);
+}
+
+static void generate_json_false(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
+{
+ fbuffer_append(buffer, "false", 5);
+}
+
+static void generate_json_true(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
+{
+ fbuffer_append(buffer, "true", 4);
+}
+
+static void generate_json_fixnum(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
+{
+ fbuffer_append_long(buffer, FIX2LONG(obj));
+}
+
+static void generate_json_bignum(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
+{
+ VALUE tmp = rb_funcall(obj, i_to_s, 0);
+ fbuffer_append_str(buffer, StringValue(tmp));
+}
+
+static void generate_json_float(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
+{
+ double value = RFLOAT_VALUE(obj);
+ char allow_nan = data->state->allow_nan;
+ if (isinf(value) || isnan(value)) {
+ /* for NaN and Infinity values we either raise an error or rely on Float#to_s. */
+ if (!allow_nan) {
+ if (data->state->strict && data->state->as_json) {
+ VALUE casted_obj = json_call_as_json(data->state, obj, Qfalse);
+ if (casted_obj != obj) {
+ increase_depth(data);
+ generate_json(buffer, data, casted_obj);
+ data->depth--;
+ return;
+ }
+ }
+ raise_generator_error(obj, "%"PRIsVALUE" not allowed in JSON", rb_funcall(obj, i_to_s, 0));
+ }
+
+ VALUE tmp = rb_funcall(obj, i_to_s, 0);
+ fbuffer_append_str(buffer, tmp);
+ return;
+ }
+
+ /* This implementation writes directly into the buffer. We reserve
+ * the 32 characters that fpconv_dtoa states as its maximum.
+ */
+ fbuffer_inc_capa(buffer, 32);
+ char* d = buffer->ptr + buffer->len;
+ int len = fpconv_dtoa(value, d);
+ /* fpconv_dtoa converts a float to its shortest string representation,
+ * but it adds a ".0" if this is a plain integer.
+ */
+ fbuffer_consumed(buffer, len);
+}
+
+static void generate_json_fragment(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
+{
+ VALUE fragment = RSTRUCT_GET(obj, 0);
+ Check_Type(fragment, T_STRING);
+ fbuffer_append_str(buffer, fragment);
+}
+
+static inline void generate_json_general(FBuffer *buffer, struct generate_json_data *data, VALUE obj, bool fallback)
+{
+ bool as_json_called = false;
+start:
+ if (obj == Qnil) {
+ generate_json_null(buffer, data, obj);
+ } else if (obj == Qfalse) {
+ generate_json_false(buffer, data, obj);
+ } else if (obj == Qtrue) {
+ generate_json_true(buffer, data, obj);
+ } else if (RB_SPECIAL_CONST_P(obj)) {
+ if (RB_FIXNUM_P(obj)) {
+ generate_json_fixnum(buffer, data, obj);
+ } else if (RB_FLONUM_P(obj)) {
+ generate_json_float(buffer, data, obj);
+ } else if (RB_STATIC_SYM_P(obj)) {
+ generate_json_symbol(buffer, data, obj);
+ } else {
+ goto general;
+ }
+ } else {
+ VALUE klass = RBASIC_CLASS(obj);
+ switch (RB_BUILTIN_TYPE(obj)) {
+ case T_BIGNUM:
+ generate_json_bignum(buffer, data, obj);
+ break;
+ case T_HASH:
+ if (fallback && klass != rb_cHash) goto general;
+ generate_json_object(buffer, data, obj);
+ break;
+ case T_ARRAY:
+ if (fallback && klass != rb_cArray) goto general;
+ generate_json_array(buffer, data, obj);
+ break;
+ case T_STRING:
+ if (fallback && klass != rb_cString) goto general;
+
+ if (RB_LIKELY(valid_json_string_p(obj))) {
+ raw_generate_json_string(buffer, data, obj);
+ } else if (as_json_called) {
+ raise_generator_error(obj, "source sequence is illegal/malformed utf-8");
+ } else {
+ obj = ensure_valid_encoding(data, obj, false, false);
+ as_json_called = true;
+ goto start;
+ }
+ break;
+ case T_SYMBOL:
+ generate_json_symbol(buffer, data, obj);
+ break;
+ case T_FLOAT:
+ if (fallback && klass != rb_cFloat) goto general;
+ generate_json_float(buffer, data, obj);
+ break;
+ case T_STRUCT:
+ if (klass != cFragment) goto general;
+ generate_json_fragment(buffer, data, obj);
+ break;
+ default:
+ general:
+ if (data->state->strict) {
+ if (RTEST(data->state->as_json) && !as_json_called) {
+ obj = json_call_as_json(data->state, obj, Qfalse);
+ as_json_called = true;
+ goto start;
+ } else {
+ raise_generator_error(obj, "%"PRIsVALUE" not allowed in JSON", CLASS_OF(obj));
+ }
+ } else {
+ generate_json_fallback(buffer, data, obj);
+ }
+ }
+ }
+}
+
+static void generate_json(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
+{
+ generate_json_general(buffer, data, obj, true);
+}
+
+static void generate_json_no_fallback(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
+{
+ generate_json_general(buffer, data, obj, false);
+}
+
+static VALUE generate_json_try(VALUE d)
+{
+ struct generate_json_data *data = (struct generate_json_data *)d;
+
+ data->func(data->buffer, data, data->obj);
+
+ return fbuffer_finalize(data->buffer);
+}
+
+static VALUE generate_json_ensure(VALUE d)
+{
+ struct generate_json_data *data = (struct generate_json_data *)d;
+ fbuffer_free(data->buffer);
+
+ return Qundef;
+}
+
+static inline VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func func, VALUE io)
+{
+ GET_STATE(self);
+
+ char stack_buffer[FBUFFER_STACK_SIZE];
+ FBuffer buffer = {
+ .io = RTEST(io) ? io : Qfalse,
+ };
+ fbuffer_stack_init(&buffer, state->buffer_initial_length, stack_buffer, FBUFFER_STACK_SIZE);
+
+ struct generate_json_data data = {
+ .buffer = &buffer,
+ .vstate = Qfalse, // don't use self as it may be frozen and its depth is mutated when calling to_json
+ .state = state,
+ .depth = state->depth,
+ .obj = obj,
+ .func = func
+ };
+ return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data);
+}
+
+/* call-seq:
+ * generate(obj) -> String
+ * generate(obj, anIO) -> anIO
+ *
+ * Generates a valid JSON document from object +obj+ and returns the
+ * result. If no valid JSON document can be created this method raises a
+ * GeneratorError exception.
+ */
+static VALUE cState_generate(int argc, VALUE *argv, VALUE self)
+{
+ rb_check_arity(argc, 1, 2);
+ VALUE obj = argv[0];
+ VALUE io = argc > 1 ? argv[1] : Qnil;
+ return cState_partial_generate(self, obj, generate_json, io);
+}
+
+/* :nodoc: */
+static VALUE cState_generate_no_fallback(int argc, VALUE *argv, VALUE self)
+{
+ rb_check_arity(argc, 1, 2);
+ VALUE obj = argv[0];
+ VALUE io = argc > 1 ? argv[1] : Qnil;
+ return cState_partial_generate(self, obj, generate_json_no_fallback, io);
+}
+
+static VALUE cState_initialize(int argc, VALUE *argv, VALUE self)
+{
+ rb_warn("The json gem extension was loaded with the stdlib ruby code. You should upgrade rubygems with `gem update --system`");
+ return self;
+}
+
+/*
+ * call-seq: initialize_copy(orig)
+ *
+ * Initializes this object from orig if it can be duplicated/cloned and returns
+ * it.
+*/
+static VALUE cState_init_copy(VALUE obj, VALUE orig)
+{
+ JSON_Generator_State *objState, *origState;
+
+ if (obj == orig) return obj;
+ GET_STATE_TO(obj, objState);
+ GET_STATE_TO(orig, origState);
+ if (!objState) rb_raise(rb_eArgError, "unallocated JSON::State");
+
+ MEMCPY(objState, origState, JSON_Generator_State, 1);
+
+ RB_OBJ_WRITTEN(obj, Qundef, objState->indent);
+ RB_OBJ_WRITTEN(obj, Qundef, objState->space);
+ RB_OBJ_WRITTEN(obj, Qundef, objState->space_before);
+ RB_OBJ_WRITTEN(obj, Qundef, objState->object_nl);
+ RB_OBJ_WRITTEN(obj, Qundef, objState->array_nl);
+ RB_OBJ_WRITTEN(obj, Qundef, objState->as_json);
+
+ return obj;
+}
+
+/*
+ * call-seq: from_state(opts)
+ *
+ * Creates a State object from _opts_, which ought to be Hash to create a
+ * new State instance configured by _opts_, something else to create an
+ * unconfigured instance. If _opts_ is a State object, it is just returned.
+ */
+static VALUE cState_from_state_s(VALUE self, VALUE opts)
+{
+ if (rb_obj_is_kind_of(opts, self)) {
+ return opts;
+ } else if (rb_obj_is_kind_of(opts, rb_cHash)) {
+ return rb_funcall(self, i_new, 1, opts);
+ } else {
+ return rb_class_new_instance(0, NULL, cState);
+ }
+}
+
+/*
+ * call-seq: indent()
+ *
+ * Returns the string that is used to indent levels in the JSON text.
+ */
+static VALUE cState_indent(VALUE self)
+{
+ GET_STATE(self);
+ return state->indent ? state->indent : rb_str_freeze(rb_utf8_str_new("", 0));
+}
+
+static VALUE string_config(VALUE config)
+{
+ if (RTEST(config)) {
+ Check_Type(config, T_STRING);
+ if (RSTRING_LEN(config)) {
+ return rb_str_new_frozen(config);
+ }
+ }
+ return Qfalse;
+}
+
+/*
+ * call-seq: indent=(indent)
+ *
+ * Sets the string that is used to indent levels in the JSON text.
+ */
+static VALUE cState_indent_set(VALUE self, VALUE indent)
+{
+ rb_check_frozen(self);
+ GET_STATE(self);
+ RB_OBJ_WRITE(self, &state->indent, string_config(indent));
+ return Qnil;
+}
+
+/*
+ * call-seq: space()
+ *
+ * Returns the string that is used to insert a space between the tokens in a JSON
+ * string.
+ */
+static VALUE cState_space(VALUE self)
+{
+ GET_STATE(self);
+ return state->space ? state->space : rb_str_freeze(rb_utf8_str_new("", 0));
+}
+
+/*
+ * call-seq: space=(space)
+ *
+ * Sets _space_ to the string that is used to insert a space between the tokens in a JSON
+ * string.
+ */
+static VALUE cState_space_set(VALUE self, VALUE space)
+{
+ rb_check_frozen(self);
+ GET_STATE(self);
+ RB_OBJ_WRITE(self, &state->space, string_config(space));
+ return Qnil;
+}
+
+/*
+ * call-seq: space_before()
+ *
+ * Returns the string that is used to insert a space before the ':' in JSON objects.
+ */
+static VALUE cState_space_before(VALUE self)
+{
+ GET_STATE(self);
+ return state->space_before ? state->space_before : rb_str_freeze(rb_utf8_str_new("", 0));
+}
+
+/*
+ * call-seq: space_before=(space_before)
+ *
+ * Sets the string that is used to insert a space before the ':' in JSON objects.
+ */
+static VALUE cState_space_before_set(VALUE self, VALUE space_before)
+{
+ rb_check_frozen(self);
+ GET_STATE(self);
+ RB_OBJ_WRITE(self, &state->space_before, string_config(space_before));
+ return Qnil;
+}
+
+/*
+ * call-seq: object_nl()
+ *
+ * This string is put at the end of a line that holds a JSON object (or
+ * Hash).
+ */
+static VALUE cState_object_nl(VALUE self)
+{
+ GET_STATE(self);
+ return state->object_nl ? state->object_nl : rb_str_freeze(rb_utf8_str_new("", 0));
+}
+
+/*
+ * call-seq: object_nl=(object_nl)
+ *
+ * This string is put at the end of a line that holds a JSON object (or
+ * Hash).
+ */
+static VALUE cState_object_nl_set(VALUE self, VALUE object_nl)
+{
+ rb_check_frozen(self);
+ GET_STATE(self);
+ RB_OBJ_WRITE(self, &state->object_nl, string_config(object_nl));
+ return Qnil;
+}
+
+/*
+ * call-seq: array_nl()
+ *
+ * This string is put at the end of a line that holds a JSON array.
+ */
+static VALUE cState_array_nl(VALUE self)
+{
+ GET_STATE(self);
+ return state->array_nl ? state->array_nl : rb_str_freeze(rb_utf8_str_new("", 0));
+}
+
+/*
+ * call-seq: array_nl=(array_nl)
+ *
+ * This string is put at the end of a line that holds a JSON array.
+ */
+static VALUE cState_array_nl_set(VALUE self, VALUE array_nl)
+{
+ rb_check_frozen(self);
+ GET_STATE(self);
+ RB_OBJ_WRITE(self, &state->array_nl, string_config(array_nl));
+ return Qnil;
+}
+
+/*
+ * call-seq: as_json()
+ *
+ * This string is put at the end of a line that holds a JSON array.
+ */
+static VALUE cState_as_json(VALUE self)
+{
+ GET_STATE(self);
+ return state->as_json;
+}
+
+/*
+ * call-seq: as_json=(as_json)
+ *
+ * This string is put at the end of a line that holds a JSON array.
+ */
+static VALUE cState_as_json_set(VALUE self, VALUE as_json)
+{
+ rb_check_frozen(self);
+ GET_STATE(self);
+ RB_OBJ_WRITE(self, &state->as_json, rb_convert_type(as_json, T_DATA, "Proc", "to_proc"));
+ return Qnil;
+}
+
+/*
+* call-seq: check_circular?
+*
+* Returns true, if circular data structures should be checked,
+* otherwise returns false.
+*/
+static VALUE cState_check_circular_p(VALUE self)
+{
+ GET_STATE(self);
+ return state->max_nesting ? Qtrue : Qfalse;
+}
+
+/*
+ * call-seq: max_nesting
+ *
+ * This integer returns the maximum level of data structure nesting in
+ * the generated JSON, max_nesting = 0 if no maximum is checked.
+ */
+static VALUE cState_max_nesting(VALUE self)
+{
+ GET_STATE(self);
+ return LONG2FIX(state->max_nesting);
+}
+
+static long long_config(VALUE num)
+{
+ return RTEST(num) ? NUM2LONG(num) : 0;
+}
+
+// depth must never be negative; reject early with a clear error.
+static long depth_config(VALUE num)
+{
+ if (!RTEST(num)) return 0;
+ long d = NUM2LONG(num);
+ if (RB_UNLIKELY(d < 0)) {
+ rb_raise(rb_eArgError, "depth must be >= 0 (got %ld)", d);
+ }
+ if (RB_UNLIKELY(d > INT_MAX)) {
+ rb_raise(rb_eArgError, "depth is too large (got %ld)", d);
+ }
+ return d;
+}
+
+/*
+ * call-seq: max_nesting=(depth)
+ *
+ * This sets the maximum level of data structure nesting in the generated JSON
+ * to the integer depth, max_nesting = 0 if no maximum should be checked.
+ */
+static VALUE cState_max_nesting_set(VALUE self, VALUE depth)
+{
+ rb_check_frozen(self);
+ GET_STATE(self);
+ state->max_nesting = long_config(depth);
+ return Qnil;
+}
+
+/*
+ * call-seq: script_safe
+ *
+ * If this boolean is true, the forward slashes will be escaped in
+ * the json output.
+ */
+static VALUE cState_script_safe(VALUE self)
+{
+ GET_STATE(self);
+ return state->script_safe ? Qtrue : Qfalse;
+}
+
+/*
+ * call-seq: script_safe=(enable)
+ *
+ * This sets whether or not the forward slashes will be escaped in
+ * the json output.
+ */
+static VALUE cState_script_safe_set(VALUE self, VALUE enable)
+{
+ rb_check_frozen(self);
+ GET_STATE(self);
+ state->script_safe = RTEST(enable);
+ return Qnil;
+}
+
+/*
+ * call-seq: strict
+ *
+ * If this boolean is false, types unsupported by the JSON format will
+ * be serialized as strings.
+ * If this boolean is true, types unsupported by the JSON format will
+ * raise a JSON::GeneratorError.
+ */
+static VALUE cState_strict(VALUE self)
+{
+ GET_STATE(self);
+ return state->strict ? Qtrue : Qfalse;
+}
+
+/*
+ * call-seq: strict=(enable)
+ *
+ * This sets whether or not to serialize types unsupported by the
+ * JSON format as strings.
+ * If this boolean is false, types unsupported by the JSON format will
+ * be serialized as strings.
+ * If this boolean is true, types unsupported by the JSON format will
+ * raise a JSON::GeneratorError.
+ */
+static VALUE cState_strict_set(VALUE self, VALUE enable)
+{
+ rb_check_frozen(self);
+ GET_STATE(self);
+ state->strict = RTEST(enable);
+ return Qnil;
+}
+
+/*
+ * call-seq: allow_nan?
+ *
+ * Returns true, if NaN, Infinity, and -Infinity should be generated, otherwise
+ * returns false.
+ */
+static VALUE cState_allow_nan_p(VALUE self)
+{
+ GET_STATE(self);
+ return state->allow_nan ? Qtrue : Qfalse;
+}
+
+/*
+ * call-seq: allow_nan=(enable)
+ *
+ * This sets whether or not to serialize NaN, Infinity, and -Infinity
+ */
+static VALUE cState_allow_nan_set(VALUE self, VALUE enable)
+{
+ rb_check_frozen(self);
+ GET_STATE(self);
+ state->allow_nan = RTEST(enable);
+ return Qnil;
+}
+
+/*
+ * call-seq: ascii_only?
+ *
+ * Returns true, if only ASCII characters should be generated. Otherwise
+ * returns false.
+ */
+static VALUE cState_ascii_only_p(VALUE self)
+{
+ GET_STATE(self);
+ return state->ascii_only ? Qtrue : Qfalse;
+}
+
+/*
+ * call-seq: ascii_only=(enable)
+ *
+ * This sets whether only ASCII characters should be generated.
+ */
+static VALUE cState_ascii_only_set(VALUE self, VALUE enable)
+{
+ rb_check_frozen(self);
+ GET_STATE(self);
+ state->ascii_only = RTEST(enable);
+ return Qnil;
+}
+
+static VALUE cState_allow_duplicate_key_p(VALUE self)
+{
+ GET_STATE(self);
+ switch (state->on_duplicate_key) {
+ case JSON_IGNORE:
+ return Qtrue;
+ case JSON_DEPRECATED:
+ return Qnil;
+ default:
+ return Qfalse;
+ }
+}
+
+/*
+ * call-seq: depth
+ *
+ * This integer returns the current depth of data structure nesting.
+ */
+static VALUE cState_depth(VALUE self)
+{
+ GET_STATE(self);
+ return LONG2FIX(state->depth);
+}
+
+/*
+ * call-seq: depth=(depth)
+ *
+ * This sets the maximum level of data structure nesting in the generated JSON
+ * to the integer depth, max_nesting = 0 if no maximum should be checked.
+ */
+static VALUE cState_depth_set(VALUE self, VALUE depth)
+{
+ rb_check_frozen(self);
+ GET_STATE(self);
+ state->depth = depth_config(depth);
+ return Qnil;
+}
+
+/*
+ * call-seq: buffer_initial_length
+ *
+ * This integer returns the current initial length of the buffer.
+ */
+static VALUE cState_buffer_initial_length(VALUE self)
+{
+ GET_STATE(self);
+ return LONG2FIX(state->buffer_initial_length);
+}
+
+static void buffer_initial_length_set(JSON_Generator_State *state, VALUE buffer_initial_length)
+{
+ Check_Type(buffer_initial_length, T_FIXNUM);
+ long initial_length = FIX2LONG(buffer_initial_length);
+ if (initial_length > 0) {
+ state->buffer_initial_length = initial_length;
+ }
+}
+
+/*
+ * call-seq: buffer_initial_length=(length)
+ *
+ * This sets the initial length of the buffer to +length+, if +length+ > 0,
+ * otherwise its value isn't changed.
+ */
+static VALUE cState_buffer_initial_length_set(VALUE self, VALUE buffer_initial_length)
+{
+ rb_check_frozen(self);
+ GET_STATE(self);
+ buffer_initial_length_set(state, buffer_initial_length);
+ return Qnil;
+}
+
+struct configure_state_data {
+ JSON_Generator_State *state;
+ VALUE vstate; // Ruby object that owns the state, or Qfalse if stack-allocated
+};
+
+static inline void state_write_value(struct configure_state_data *data, VALUE *field, VALUE value)
+{
+ if (RTEST(data->vstate)) {
+ RB_OBJ_WRITE(data->vstate, field, value);
+ } else {
+ *field = value;
+ }
+}
+
+static int configure_state_i(VALUE key, VALUE val, VALUE _arg)
+{
+ struct configure_state_data *data = (struct configure_state_data *)_arg;
+ JSON_Generator_State *state = data->state;
+
+ if (key == sym_indent) { state_write_value(data, &state->indent, string_config(val)); }
+ else if (key == sym_space) { state_write_value(data, &state->space, string_config(val)); }
+ else if (key == sym_space_before) { state_write_value(data, &state->space_before, string_config(val)); }
+ else if (key == sym_object_nl) { state_write_value(data, &state->object_nl, string_config(val)); }
+ else if (key == sym_array_nl) { state_write_value(data, &state->array_nl, string_config(val)); }
+ else if (key == sym_max_nesting) { state->max_nesting = long_config(val); }
+ else if (key == sym_allow_nan) { state->allow_nan = RTEST(val); }
+ else if (key == sym_ascii_only) { state->ascii_only = RTEST(val); }
+ else if (key == sym_depth) { state->depth = depth_config(val); }
+ else if (key == sym_buffer_initial_length) { buffer_initial_length_set(state, val); }
+ else if (key == sym_script_safe) { state->script_safe = RTEST(val); }
+ else if (key == sym_escape_slash) { state->script_safe = RTEST(val); }
+ else if (key == sym_strict) { state->strict = RTEST(val); }
+ else if (key == sym_allow_duplicate_key) { state->on_duplicate_key = RTEST(val) ? JSON_IGNORE : JSON_RAISE; }
+ else if (key == sym_as_json) {
+ VALUE proc = RTEST(val) ? rb_convert_type(val, T_DATA, "Proc", "to_proc") : Qfalse;
+ state->as_json_single_arg = proc && rb_proc_arity(proc) == 1;
+ state_write_value(data, &state->as_json, proc);
+ }
+ return ST_CONTINUE;
+}
+
+static void configure_state(JSON_Generator_State *state, VALUE vstate, VALUE config)
+{
+ if (!RTEST(config)) return;
+
+ Check_Type(config, T_HASH);
+
+ if (!RHASH_SIZE(config)) return;
+
+ struct configure_state_data data = {
+ .state = state,
+ .vstate = vstate
+ };
+
+ // We assume in most cases few keys are set so it's faster to go over
+ // the provided keys than to check all possible keys.
+ rb_hash_foreach(config, configure_state_i, (VALUE)&data);
+}
+
+static VALUE cState_configure(VALUE self, VALUE opts)
+{
+ rb_check_frozen(self);
+ GET_STATE(self);
+ configure_state(state, self, opts);
+ return self;
+}
+
+static VALUE cState_m_do_generate(VALUE klass, VALUE obj, VALUE opts, VALUE io, generator_func func)
+{
+ JSON_Generator_State state = {0};
+ state_init(&state);
+ configure_state(&state, Qfalse, opts);
+
+ char stack_buffer[FBUFFER_STACK_SIZE];
+ FBuffer buffer = {
+ .io = RTEST(io) ? io : Qfalse,
+ };
+ fbuffer_stack_init(&buffer, state.buffer_initial_length, stack_buffer, FBUFFER_STACK_SIZE);
+
+ struct generate_json_data data = {
+ .buffer = &buffer,
+ .vstate = Qfalse,
+ .state = &state,
+ .depth = state.depth,
+ .obj = obj,
+ .func = func,
+ };
+ return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data);
+}
+
+static VALUE cState_m_generate(VALUE klass, VALUE obj, VALUE opts, VALUE io)
+{
+ return cState_m_do_generate(klass, obj, opts, io, generate_json);
+}
+
+static VALUE cState_m_generate_no_fallback(VALUE klass, VALUE obj, VALUE opts, VALUE io)
+{
+ return cState_m_do_generate(klass, obj, opts, io, generate_json_no_fallback);
+}
+
+void Init_generator(void)
+{
+#ifdef HAVE_RB_EXT_RACTOR_SAFE
+ rb_ext_ractor_safe(true);
+#endif
+
+#undef rb_intern
+ rb_require("json/common");
+
+ mJSON = rb_define_module("JSON");
+
+ rb_global_variable(&cFragment);
+ cFragment = rb_const_get(mJSON, rb_intern("Fragment"));
+
+ VALUE mExt = rb_define_module_under(mJSON, "Ext");
+ VALUE mGenerator = rb_define_module_under(mExt, "Generator");
+
+ rb_global_variable(&eGeneratorError);
+ eGeneratorError = rb_path2class("JSON::GeneratorError");
+
+ rb_global_variable(&eNestingError);
+ eNestingError = rb_path2class("JSON::NestingError");
+
+ cState = rb_define_class_under(mGenerator, "State", rb_cObject);
+ rb_define_alloc_func(cState, cState_s_allocate);
+ rb_define_singleton_method(cState, "from_state", cState_from_state_s, 1);
+ rb_define_method(cState, "initialize", cState_initialize, -1);
+ rb_define_alias(cState, "initialize", "initialize"); // avoid method redefinition warnings
+ rb_define_private_method(cState, "_configure", cState_configure, 1);
+
+ rb_define_method(cState, "initialize_copy", cState_init_copy, 1);
+ rb_define_method(cState, "indent", cState_indent, 0);
+ rb_define_method(cState, "indent=", cState_indent_set, 1);
+ rb_define_method(cState, "space", cState_space, 0);
+ rb_define_method(cState, "space=", cState_space_set, 1);
+ rb_define_method(cState, "space_before", cState_space_before, 0);
+ rb_define_method(cState, "space_before=", cState_space_before_set, 1);
+ rb_define_method(cState, "object_nl", cState_object_nl, 0);
+ rb_define_method(cState, "object_nl=", cState_object_nl_set, 1);
+ rb_define_method(cState, "array_nl", cState_array_nl, 0);
+ rb_define_method(cState, "array_nl=", cState_array_nl_set, 1);
+ rb_define_method(cState, "as_json", cState_as_json, 0);
+ rb_define_method(cState, "as_json=", cState_as_json_set, 1);
+ rb_define_method(cState, "max_nesting", cState_max_nesting, 0);
+ rb_define_method(cState, "max_nesting=", cState_max_nesting_set, 1);
+ rb_define_method(cState, "script_safe", cState_script_safe, 0);
+ rb_define_method(cState, "script_safe?", cState_script_safe, 0);
+ rb_define_method(cState, "script_safe=", cState_script_safe_set, 1);
+ rb_define_alias(cState, "escape_slash", "script_safe");
+ rb_define_alias(cState, "escape_slash?", "script_safe?");
+ rb_define_alias(cState, "escape_slash=", "script_safe=");
+ rb_define_method(cState, "strict", cState_strict, 0);
+ rb_define_method(cState, "strict?", cState_strict, 0);
+ rb_define_method(cState, "strict=", cState_strict_set, 1);
+ rb_define_method(cState, "check_circular?", cState_check_circular_p, 0);
+ rb_define_method(cState, "allow_nan?", cState_allow_nan_p, 0);
+ rb_define_method(cState, "allow_nan=", cState_allow_nan_set, 1);
+ rb_define_method(cState, "ascii_only?", cState_ascii_only_p, 0);
+ rb_define_method(cState, "ascii_only=", cState_ascii_only_set, 1);
+ rb_define_method(cState, "depth", cState_depth, 0);
+ rb_define_method(cState, "depth=", cState_depth_set, 1);
+ rb_define_method(cState, "buffer_initial_length", cState_buffer_initial_length, 0);
+ rb_define_method(cState, "buffer_initial_length=", cState_buffer_initial_length_set, 1);
+ rb_define_method(cState, "generate", cState_generate, -1);
+ rb_define_method(cState, "_generate_no_fallback", cState_generate_no_fallback, -1);
+
+ rb_define_private_method(cState, "allow_duplicate_key?", cState_allow_duplicate_key_p, 0);
+
+ rb_define_singleton_method(cState, "generate", cState_m_generate, 3);
+ rb_define_singleton_method(cState, "_generate_no_fallback", cState_m_generate_no_fallback, 3);
+
+ rb_global_variable(&Encoding_UTF_8);
+ Encoding_UTF_8 = rb_const_get(rb_path2class("Encoding"), rb_intern("UTF_8"));
+
+ i_to_s = rb_intern("to_s");
+ i_to_json = rb_intern("to_json");
+ i_new = rb_intern("new");
+ i_encode = rb_intern("encode");
+
+ sym_indent = ID2SYM(rb_intern("indent"));
+ sym_space = ID2SYM(rb_intern("space"));
+ sym_space_before = ID2SYM(rb_intern("space_before"));
+ sym_object_nl = ID2SYM(rb_intern("object_nl"));
+ sym_array_nl = ID2SYM(rb_intern("array_nl"));
+ sym_max_nesting = ID2SYM(rb_intern("max_nesting"));
+ sym_allow_nan = ID2SYM(rb_intern("allow_nan"));
+ sym_ascii_only = ID2SYM(rb_intern("ascii_only"));
+ sym_depth = ID2SYM(rb_intern("depth"));
+ sym_buffer_initial_length = ID2SYM(rb_intern("buffer_initial_length"));
+ sym_script_safe = ID2SYM(rb_intern("script_safe"));
+ sym_escape_slash = ID2SYM(rb_intern("escape_slash"));
+ sym_strict = ID2SYM(rb_intern("strict"));
+ sym_as_json = ID2SYM(rb_intern("as_json"));
+ sym_allow_duplicate_key = ID2SYM(rb_intern("allow_duplicate_key"));
+
+ usascii_encindex = rb_usascii_encindex();
+ utf8_encindex = rb_utf8_encindex();
+ binary_encindex = rb_ascii8bit_encindex();
+
+ rb_require("json/ext/generator/state");
+
+ simd_impl = find_simd_implementation();
+}
diff --git a/ext/json/json.gemspec b/ext/json/json.gemspec
new file mode 100644
index 0000000000..5575731025
--- /dev/null
+++ b/ext/json/json.gemspec
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+version = File.foreach(File.join(__dir__, "lib/json/version.rb")) do |line|
+ /^\s*VERSION\s*=\s*'(.*)'/ =~ line and break $1
+end rescue nil
+
+spec = Gem::Specification.new do |s|
+ java_ext = Gem::Platform === s.platform && s.platform =~ 'java' || RUBY_ENGINE == 'jruby'
+
+ s.name = "json"
+ s.version = version
+
+ s.summary = "JSON Implementation for Ruby"
+ s.homepage = "https://github.com/ruby/json"
+ s.metadata = {
+ 'bug_tracker_uri' => 'https://github.com/ruby/json/issues',
+ 'changelog_uri' => 'https://github.com/ruby/json/blob/master/CHANGES.md',
+ 'documentation_uri' => 'https://docs.ruby-lang.org/en/master/JSON.html',
+ 'homepage_uri' => s.homepage,
+ 'source_code_uri' => 'https://github.com/ruby/json',
+ }
+
+ s.required_ruby_version = Gem::Requirement.new(">= 2.7")
+
+ if java_ext
+ s.description = "A JSON implementation as a JRuby extension."
+ s.author = "Daniel Luz"
+ s.email = "dev+ruby@mernen.com"
+ else
+ s.description = "This is a JSON implementation as a Ruby extension in C."
+ s.authors = ["Florian Frank"]
+ s.email = "flori@ping.de"
+ end
+
+ s.licenses = ["Ruby"]
+
+ s.extra_rdoc_files = ["README.md"]
+ s.rdoc_options = ["--title", "JSON implementation for Ruby", "--main", "README.md"]
+
+ s.files = [
+ "CHANGES.md",
+ "COPYING",
+ "BSDL",
+ "LEGAL",
+ "README.md",
+ "json.gemspec",
+ ] + Dir.glob("lib/**/*.rb", base: File.expand_path("..", __FILE__))
+
+ if java_ext
+ s.platform = 'java'
+ s.files += Dir["lib/json/ext/**/*.jar"]
+ else
+ s.extensions = Dir["ext/json/**/extconf.rb"]
+ s.files += Dir["ext/json/**/*.{c,h,rb}"]
+ end
+end
+
+if RUBY_ENGINE == 'jruby' && $0 == __FILE__
+ Gem::Builder.new(spec).build
+else
+ spec
+end
diff --git a/ext/json/json.h b/ext/json/json.h
new file mode 100644
index 0000000000..8737989a6c
--- /dev/null
+++ b/ext/json/json.h
@@ -0,0 +1,116 @@
+#ifndef _JSON_H_
+#define _JSON_H_
+
+#include "ruby.h"
+#include "ruby/encoding.h"
+#include <stdint.h>
+
+#ifndef RBIMPL_ASSERT_OR_ASSUME
+# define RBIMPL_ASSERT_OR_ASSUME(x)
+#endif
+
+#if defined(RUBY_DEBUG) && RUBY_DEBUG
+# define JSON_ASSERT RUBY_ASSERT
+#else
+# ifdef JSON_DEBUG
+# include <assert.h>
+# define JSON_ASSERT(x) assert(x)
+# else
+# define JSON_ASSERT(x)
+# endif
+#endif
+
+/* shims */
+
+#if SIZEOF_UINT64_T == SIZEOF_LONG_LONG
+# define INT64T2NUM(x) LL2NUM(x)
+# define UINT64T2NUM(x) ULL2NUM(x)
+#elif SIZEOF_UINT64_T == SIZEOF_LONG
+# define INT64T2NUM(x) LONG2NUM(x)
+# define UINT64T2NUM(x) ULONG2NUM(x)
+#else
+# error No uint64_t conversion
+#endif
+
+/* This is the fallback definition from Ruby 3.4 */
+#ifndef RBIMPL_STDBOOL_H
+#if defined(__cplusplus)
+# if defined(HAVE_STDBOOL_H) && (__cplusplus >= 201103L)
+# include <cstdbool>
+# endif
+#elif defined(HAVE_STDBOOL_H)
+# include <stdbool.h>
+#elif !defined(HAVE__BOOL)
+typedef unsigned char _Bool;
+# define bool _Bool
+# define true ((_Bool)+1)
+# define false ((_Bool)+0)
+# define __bool_true_false_are_defined
+#endif
+#endif
+
+#ifndef HAVE_RB_EXT_RACTOR_SAFE
+# undef RUBY_TYPED_FROZEN_SHAREABLE
+# define RUBY_TYPED_FROZEN_SHAREABLE 0
+#endif
+
+#ifdef RUBY_TYPED_EMBEDDABLE
+# define HAVE_RUBY_TYPED_EMBEDDABLE 1
+#else
+# ifdef HAVE_CONST_RUBY_TYPED_EMBEDDABLE
+# define RUBY_TYPED_EMBEDDABLE RUBY_TYPED_EMBEDDABLE
+# define HAVE_RUBY_TYPED_EMBEDDABLE 1
+# else
+# define RUBY_TYPED_EMBEDDABLE 0
+# endif
+#endif
+
+#ifndef NORETURN
+#if defined(__has_attribute) && __has_attribute(noreturn)
+#define NORETURN(x) __attribute__((noreturn)) x
+#else
+#define NORETURN(x) x
+#endif
+#endif
+
+#ifndef NOINLINE
+#if defined(__has_attribute) && __has_attribute(noinline)
+#define NOINLINE(x) __attribute__((noinline)) x
+#else
+#define NOINLINE(x) x
+#endif
+#endif
+
+#ifndef ALWAYS_INLINE
+#if defined(__has_attribute) && __has_attribute(always_inline)
+#define ALWAYS_INLINE(x) inline __attribute__((always_inline)) x
+#else
+#define ALWAYS_INLINE(x) inline x
+#endif
+#endif
+
+#ifndef RB_UNLIKELY
+#define RB_UNLIKELY(expr) expr
+#endif
+
+#ifndef RB_LIKELY
+#define RB_LIKELY(expr) expr
+#endif
+
+#ifndef MAYBE_UNUSED
+# define MAYBE_UNUSED(x) x
+#endif
+
+#ifdef RUBY_DEBUG
+#ifndef JSON_DEBUG
+#define JSON_DEBUG RUBY_DEBUG
+#endif
+#endif
+
+#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ && INTPTR_MAX == INT64_MAX
+#define JSON_CPU_LITTLE_ENDIAN_64BITS 1
+#else
+#define JSON_CPU_LITTLE_ENDIAN_64BITS 0
+#endif
+
+#endif // _JSON_H_
diff --git a/ext/json/lib/json.rb b/ext/json/lib/json.rb
index 3b0b711550..26d601926f 100644
--- a/ext/json/lib/json.rb
+++ b/ext/json/lib/json.rb
@@ -1,235 +1,675 @@
+# frozen_string_literal: true
require 'json/common'
-# = json - JSON for Ruby
+
+##
+# = JavaScript \Object Notation (\JSON)
+#
+# \JSON is a lightweight data-interchange format.
#
-# == Description
+# \JSON is easy for us humans to read and write,
+# and equally simple for machines to read (parse) and write (generate).
#
-# This is a implementation of the JSON specification according to RFC 4627
-# (http://www.ietf.org/rfc/rfc4627.txt). Starting from version 1.0.0 on there
-# will be two variants available:
+# \JSON is language-independent, making it an ideal interchange format
+# for applications in differing programming languages
+# and on differing operating systems.
#
-# * A pure ruby variant, that relies on the iconv and the stringscan
-# extensions, which are both part of the ruby standard library.
-# * The quite a bit faster C extension variant, which is in parts implemented
-# in C and comes with its own unicode conversion functions and a parser
-# generated by the ragel state machine compiler
-# (http://www.cs.queensu.ca/~thurston/ragel).
+# == \JSON Values
#
-# Both variants of the JSON generator escape all non-ASCII an control
-# characters with \uXXXX escape sequences, and support UTF-16 surrogate pairs
-# in order to be able to generate the whole range of unicode code points. This
-# means that generated JSON text is encoded as UTF-8 (because ASCII is a subset
-# of UTF-8) and at the same time avoids decoding problems for receiving
-# endpoints, that don't expect UTF-8 encoded texts. On the negative side this
-# may lead to a bit longer strings than necessarry.
+# A \JSON value is one of the following:
+# - Double-quoted text: <tt>"foo"</tt>.
+# - Number: +1+, +1.0+, +2.0e2+.
+# - Boolean: +true+, +false+.
+# - Null: +null+.
+# - \Array: an ordered list of values, enclosed by square brackets:
+# ["foo", 1, 1.0, 2.0e2, true, false, null]
#
-# All strings, that are to be encoded as JSON strings, should be UTF-8 byte
-# sequences on the Ruby side. To encode raw binary strings, that aren't UTF-8
-# encoded, please use the to_json_raw_object method of String (which produces
-# an object, that contains a byte array) and decode the result on the receiving
-# endpoint.
+# - \Object: a collection of name/value pairs, enclosed by curly braces;
+# each name is double-quoted text;
+# the values may be any \JSON values:
+# {"a": "foo", "b": 1, "c": 1.0, "d": 2.0e2, "e": true, "f": false, "g": null}
#
-# == Author
+# A \JSON array or object may contain nested arrays, objects, and scalars
+# to any depth:
+# {"foo": {"bar": 1, "baz": 2}, "bat": [0, 1, 2]}
+# [{"foo": 0, "bar": 1}, ["baz", 2]]
#
-# Florian Frank <mailto:flori@ping.de>
+# == Using \Module \JSON
#
-# == License
+# To make module \JSON available in your code, begin with:
+# require 'json'
#
-# This software is distributed under the same license as Ruby itself, see
-# http://www.ruby-lang.org/en/LICENSE.txt.
+# All examples here assume that this has been done.
+#
+# === Parsing \JSON
+#
+# You can parse a \String containing \JSON data using
+# either of two methods:
+# - <tt>JSON.parse(source, opts)</tt>
+# - <tt>JSON.parse!(source, opts)</tt>
+#
+# where
+# - +source+ is a Ruby object.
+# - +opts+ is a \Hash object containing options
+# that control both input allowed and output formatting.
+#
+# The difference between the two methods
+# is that JSON.parse! omits some checks
+# and may not be safe for some +source+ data;
+# use it only for data from trusted sources.
+# Use the safer method JSON.parse for less trusted sources.
+#
+# ==== Parsing \JSON Arrays
+#
+# When +source+ is a \JSON array, JSON.parse by default returns a Ruby \Array:
+# json = '["foo", 1, 1.0, 2.0e2, true, false, null]'
+# ruby = JSON.parse(json)
+# ruby # => ["foo", 1, 1.0, 200.0, true, false, nil]
+# ruby.class # => Array
+#
+# The \JSON array may contain nested arrays, objects, and scalars
+# to any depth:
+# json = '[{"foo": 0, "bar": 1}, ["baz", 2]]'
+# JSON.parse(json) # => [{"foo"=>0, "bar"=>1}, ["baz", 2]]
+#
+# ==== Parsing \JSON \Objects
+#
+# When the source is a \JSON object, JSON.parse by default returns a Ruby \Hash:
+# json = '{"a": "foo", "b": 1, "c": 1.0, "d": 2.0e2, "e": true, "f": false, "g": null}'
+# ruby = JSON.parse(json)
+# ruby # => {"a"=>"foo", "b"=>1, "c"=>1.0, "d"=>200.0, "e"=>true, "f"=>false, "g"=>nil}
+# ruby.class # => Hash
+#
+# The \JSON object may contain nested arrays, objects, and scalars
+# to any depth:
+# json = '{"foo": {"bar": 1, "baz": 2}, "bat": [0, 1, 2]}'
+# JSON.parse(json) # => {"foo"=>{"bar"=>1, "baz"=>2}, "bat"=>[0, 1, 2]}
+#
+# ==== Parsing \JSON Scalars
+#
+# When the source is a \JSON scalar (not an array or object),
+# JSON.parse returns a Ruby scalar.
+#
+# \String:
+# ruby = JSON.parse('"foo"')
+# ruby # => 'foo'
+# ruby.class # => String
+# \Integer:
+# ruby = JSON.parse('1')
+# ruby # => 1
+# ruby.class # => Integer
+# \Float:
+# ruby = JSON.parse('1.0')
+# ruby # => 1.0
+# ruby.class # => Float
+# ruby = JSON.parse('2.0e2')
+# ruby # => 200
+# ruby.class # => Float
+# Boolean:
+# ruby = JSON.parse('true')
+# ruby # => true
+# ruby.class # => TrueClass
+# ruby = JSON.parse('false')
+# ruby # => false
+# ruby.class # => FalseClass
+# Null:
+# ruby = JSON.parse('null')
+# ruby # => nil
+# ruby.class # => NilClass
+#
+# ==== Parsing Options
+#
+# ====== Input Options
+#
+# Option +max_nesting+ (\Integer) specifies the maximum nesting depth allowed;
+# defaults to +100+; specify +false+ to disable depth checking.
+#
+# With the default, +false+:
+# source = '[0, [1, [2, [3]]]]'
+# ruby = JSON.parse(source)
+# ruby # => [0, [1, [2, [3]]]]
+# Too deep:
+# # Raises JSON::NestingError (nesting of 2 is too deep):
+# JSON.parse(source, {max_nesting: 1})
+# Bad value:
+# # Raises TypeError (wrong argument type Symbol (expected Fixnum)):
+# JSON.parse(source, {max_nesting: :foo})
+#
+# ---
+#
+# Option +allow_duplicate_key+ specifies whether duplicate keys in objects
+# should be ignored or cause an error to be raised:
+#
+# When not specified:
+# # The last value is used and a deprecation warning emitted.
+# JSON.parse('{"a": 1, "a":2}') => {"a" => 2}
+# # warning: detected duplicate keys in JSON object.
+# # This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`
+#
+# When set to `+true+`
+# # The last value is used.
+# JSON.parse('{"a": 1, "a":2}') => {"a" => 2}
+#
+# When set to `+false+`, the future default:
+# JSON.parse('{"a": 1, "a":2}') => duplicate key at line 1 column 1 (JSON::ParserError)
+#
+# ---
+#
+# Option +allow_nan+ (boolean) specifies whether to allow
+# NaN, Infinity, and MinusInfinity in +source+;
+# defaults to +false+.
+#
+# With the default, +false+:
+# # Raises JSON::ParserError (225: unexpected token at '[NaN]'):
+# JSON.parse('[NaN]')
+# # Raises JSON::ParserError (232: unexpected token at '[Infinity]'):
+# JSON.parse('[Infinity]')
+# # Raises JSON::ParserError (248: unexpected token at '[-Infinity]'):
+# JSON.parse('[-Infinity]')
+# Allow:
+# source = '[NaN, Infinity, -Infinity]'
+# ruby = JSON.parse(source, {allow_nan: true})
+# ruby # => [NaN, Infinity, -Infinity]
+#
+# ---
+#
+# Option +allow_trailing_comma+ (boolean) specifies whether to allow
+# trailing commas in objects and arrays;
+# defaults to +false+.
+#
+# With the default, +false+:
+# JSON.parse('[1,]') # unexpected character: ']' at line 1 column 4 (JSON::ParserError)
+#
+# When enabled:
+# JSON.parse('[1,]', allow_trailing_comma: true) # => [1]
+#
+# ---
+#
+# Option +allow_control_characters+ (boolean) specifies whether to allow
+# unescaped ASCII control characters, such as newlines, in strings;
+# defaults to +false+.
+#
+# With the default, +false+:
+# JSON.parse(%{"Hello\nWorld"}) # invalid ASCII control character in string (JSON::ParserError)
+#
+# When enabled:
+# JSON.parse(%{"Hello\nWorld"}, allow_control_characters: true) # => "Hello\nWorld"
+#
+# ---
+#
+# Option +allow_invalid_escape+ (boolean) specifies whether to ignore backslahes that are followed
+# by an invalid escape character in strings;
+# defaults to +false+.
+#
+# With the default, +false+:
+# JSON.parse('"Hell\o"') # invalid escape character in string (JSON::ParserError)
+#
+# When enabled:
+# JSON.parse('"Hell\o"', allow_invalid_escape: true) # => "Hello"
+#
+# ====== Output Options
+#
+# Option +freeze+ (boolean) specifies whether the returned objects will be frozen;
+# defaults to +false+.
+#
+# Option +symbolize_names+ (boolean) specifies whether returned \Hash keys
+# should be Symbols;
+# defaults to +false+ (use Strings).
#
-# == Download
+# With the default, +false+:
+# source = '{"a": "foo", "b": 1.0, "c": true, "d": false, "e": null}'
+# ruby = JSON.parse(source)
+# ruby # => {"a"=>"foo", "b"=>1.0, "c"=>true, "d"=>false, "e"=>nil}
+# Use Symbols:
+# ruby = JSON.parse(source, {symbolize_names: true})
+# ruby # => {:a=>"foo", :b=>1.0, :c=>true, :d=>false, :e=>nil}
#
-# The latest version of this library can be downloaded at
+# ---
+#
+# Option +object_class+ (\Class) specifies the Ruby class to be used
+# for each \JSON object;
+# defaults to \Hash.
#
-# * http://rubyforge.org/frs?group_id=953
+# With the default, \Hash:
+# source = '{"a": "foo", "b": 1.0, "c": true, "d": false, "e": null}'
+# ruby = JSON.parse(source)
+# ruby.class # => Hash
+# Use class \OpenStruct:
+# ruby = JSON.parse(source, {object_class: OpenStruct})
+# ruby # => #<OpenStruct a="foo", b=1.0, c=true, d=false, e=nil>
+#
+# ---
#
-# Online Documentation should be located at
+# Option +array_class+ (\Class) specifies the Ruby class to be used
+# for each \JSON array;
+# defaults to \Array.
#
-# * http://json.rubyforge.org
+# With the default, \Array:
+# source = '["foo", 1.0, true, false, null]'
+# ruby = JSON.parse(source)
+# ruby.class # => Array
+# Use class \Set:
+# ruby = JSON.parse(source, {array_class: Set})
+# ruby # => #<Set: {"foo", 1.0, true, false, nil}>
#
-# == Usage
-#
-# To use JSON you can
-# require 'json'
-# to load the installed variant (either the extension 'json' or the pure
-# variant 'json_pure'). If you have installed the extension variant, you can
-# pick either the extension variant or the pure variant by typing
-# require 'json/ext'
-# or
-# require 'json/pure'
+# ---
+#
+# Option +create_additions+ (boolean) specifies whether to use \JSON additions in parsing.
+# See {\JSON Additions}[#module-JSON-label-JSON+Additions].
+#
+# === Generating \JSON
+#
+# To generate a Ruby \String containing \JSON data,
+# use method <tt>JSON.generate(source, opts)</tt>, where
+# - +source+ is a Ruby object.
+# - +opts+ is a \Hash object containing options
+# that control both input allowed and output formatting.
+#
+# ==== Generating \JSON from Arrays
+#
+# When the source is a Ruby \Array, JSON.generate returns
+# a \String containing a \JSON array:
+# ruby = [0, 's', :foo]
+# json = JSON.generate(ruby)
+# json # => '[0,"s","foo"]'
#
-# You can choose to load a set of common additions to ruby core's objects if
-# you
-# require 'json/add/core'
+# The Ruby \Array array may contain nested arrays, hashes, and scalars
+# to any depth:
+# ruby = [0, [1, 2], {foo: 3, bar: 4}]
+# json = JSON.generate(ruby)
+# json # => '[0,[1,2],{"foo":3,"bar":4}]'
#
-# After requiring this you can, e. g., serialise/deserialise Ruby ranges:
-#
-# JSON JSON(1..10) # => 1..10
+# ==== Generating \JSON from Hashes
#
-# To find out how to add JSON support to other or your own classes, read the
-# Examples section below.
-#
-# To get the best compatibility to rails' JSON implementation, you can
-# require 'json/add/rails'
-#
-# Both of the additions attempt to require 'json' (like above) first, if it has
-# not been required yet.
-#
-# == Speed Comparisons
-#
-# I have created some benchmark results (see the benchmarks subdir of the
-# package) for the JSON-Parser to estimate the speed up in the C extension:
-#
-# JSON::Pure::Parser:: 28.90 calls/second
-# JSON::Ext::Parser:: 505.50 calls/second
-#
-# This is ca. <b>17.5</b> times the speed of the pure Ruby implementation.
-#
-# I have benchmarked the JSON-Generator as well. This generates a few more
-# values, because there are different modes, that also influence the achieved
-# speed:
-#
-# * JSON::Pure::Generator:
-# generate:: 35.06 calls/second
-# pretty_generate:: 34.00 calls/second
-# fast_generate:: 41.06 calls/second
-#
-# * JSON::Ext::Generator:
-# generate:: 492.11 calls/second
-# pretty_generate:: 348.85 calls/second
-# fast_generate:: 541.60 calls/second
-#
-# * Speedup Ext/Pure:
-# generate safe:: 14.0 times
-# generate pretty:: 10.3 times
-# generate fast:: 13.2 times
-#
-# The rails framework includes a generator as well, also it seems to be rather
-# slow: I measured only 23.87 calls/second which is slower than any of my pure
-# generator results. Here a comparison of the different speedups with the Rails
-# measurement as the divisor:
-#
-# * Speedup Pure/Rails:
-# generate safe:: 1.5 times
-# generate pretty:: 1.4 times
-# generate fast:: 1.7 times
-#
-# * Speedup Ext/Rails:
-# generate safe:: 20.6 times
-# generate pretty:: 14.6 times
-# generate fast:: 22.7 times
-#
-# To achieve the fastest JSON text output, you can use the
-# fast_generate/fast_unparse methods. Beware, that this will disable the
-# checking for circular Ruby data structures, which may cause JSON to go into
-# an infinite loop.
-#
-# == Examples
-#
-# To create a JSON text from a ruby data structure, you
-# can call JSON.generate (or JSON.unparse) like that:
-#
-# json = JSON.generate [1, 2, {"a"=>3.141}, false, true, nil, 4..10]
-# # => "[1,2,{\"a\":3.141},false,true,null,\"4..10\"]"
-#
-# To create a valid JSON text you have to make sure, that the output is
-# embedded in either a JSON array [] or a JSON object {}. The easiest way to do
-# this, is by putting your values in a Ruby Array or Hash instance.
-#
-# To get back a ruby data structure from a JSON text, you have to call
-# JSON.parse on it:
-#
-# JSON.parse json
-# # => [1, 2, {"a"=>3.141}, false, true, nil, "4..10"]
-#
-# Note, that the range from the original data structure is a simple
-# string now. The reason for this is, that JSON doesn't support ranges
-# or arbitrary classes. In this case the json library falls back to call
-# Object#to_json, which is the same as #to_s.to_json.
-#
-# It's possible to add JSON support serialization to arbitrary classes by
-# simply implementing a more specialized version of the #to_json method, that
-# should return a JSON object (a hash converted to JSON with #to_json) like
-# this (don't forget the *a for all the arguments):
-#
-# class Range
-# def to_json(*a)
-# {
-# 'json_class' => self.class.name, # = 'Range'
-# 'data' => [ first, last, exclude_end? ]
-# }.to_json(*a)
-# end
-# end
-#
-# The hash key 'json_class' is the class, that will be asked to deserialise the
-# JSON representation later. In this case it's 'Range', but any namespace of
-# the form 'A::B' or '::A::B' will do. All other keys are arbitrary and can be
-# used to store the necessary data to configure the object to be deserialised.
-#
-# If a the key 'json_class' is found in a JSON object, the JSON parser checks
-# if the given class responds to the json_create class method. If so, it is
-# called with the JSON object converted to a Ruby hash. So a range can
-# be deserialised by implementing Range.json_create like this:
-#
-# class Range
-# def self.json_create(o)
-# new(*o['data'])
-# end
-# end
-#
-# Now it possible to serialise/deserialise ranges as well:
-#
-# json = JSON.generate [1, 2, {"a"=>3.141}, false, true, nil, 4..10]
-# # => "[1,2,{\"a\":3.141},false,true,null,{\"json_class\":\"Range\",\"data\":[4,10,false]}]"
-# JSON.parse json
-# # => [1, 2, {"a"=>3.141}, false, true, nil, 4..10]
-#
-# JSON.generate always creates the shortest possible string representation of a
-# ruby data structure in one line. This good for data storage or network
-# protocols, but not so good for humans to read. Fortunately there's also
-# JSON.pretty_generate (or JSON.pretty_generate) that creates a more
-# readable output:
-#
-# puts JSON.pretty_generate([1, 2, {"a"=>3.141}, false, true, nil, 4..10])
-# [
-# 1,
-# 2,
-# {
-# "a": 3.141
-# },
-# false,
-# true,
-# null,
-# {
-# "json_class": "Range",
-# "data": [
-# 4,
-# 10,
-# false
-# ]
-# }
-# ]
-#
-# There are also the methods Kernel#j for unparse, and Kernel#jj for
-# pretty_unparse output to the console, that work analogous to Core Ruby's p
-# and the pp library's pp methods.
-#
-# The script tools/server.rb contains a small example if you want to test, how
-# receiving a JSON object from a webrick server in your browser with the
-# javasript prototype library (http://www.prototypejs.org) works.
+# When the source is a Ruby \Hash, JSON.generate returns
+# a \String containing a \JSON object:
+# ruby = {foo: 0, bar: 's', baz: :bat}
+# json = JSON.generate(ruby)
+# json # => '{"foo":0,"bar":"s","baz":"bat"}'
+#
+# The Ruby \Hash array may contain nested arrays, hashes, and scalars
+# to any depth:
+# ruby = {foo: [0, 1], bar: {baz: 2, bat: 3}, bam: :bad}
+# json = JSON.generate(ruby)
+# json # => '{"foo":[0,1],"bar":{"baz":2,"bat":3},"bam":"bad"}'
+#
+# ==== Generating \JSON from Other Objects
+#
+# When the source is neither an \Array nor a \Hash,
+# the generated \JSON data depends on the class of the source.
+#
+# When the source is a Ruby \Integer or \Float, JSON.generate returns
+# a \String containing a \JSON number:
+# JSON.generate(42) # => '42'
+# JSON.generate(0.42) # => '0.42'
+#
+# When the source is a Ruby \String, JSON.generate returns
+# a \String containing a \JSON string (with double-quotes):
+# JSON.generate('A string') # => '"A string"'
+#
+# When the source is +true+, +false+ or +nil+, JSON.generate returns
+# a \String containing the corresponding \JSON token:
+# JSON.generate(true) # => 'true'
+# JSON.generate(false) # => 'false'
+# JSON.generate(nil) # => 'null'
+#
+# When the source is none of the above, JSON.generate returns
+# a \String containing a \JSON string representation of the source:
+# JSON.generate(:foo) # => '"foo"'
+# JSON.generate(Complex(0, 0)) # => '"0+0i"'
+# JSON.generate(Dir.new('.')) # => '"#<Dir>"'
+#
+# ==== Generating Options
+#
+# ====== Input Options
+#
+# Option +allow_nan+ (boolean) specifies whether
+# +NaN+, +Infinity+, and <tt>-Infinity</tt> may be generated;
+# defaults to +false+.
+#
+# With the default, +false+:
+# # Raises JSON::GeneratorError (920: NaN not allowed in JSON):
+# JSON.generate(JSON::NaN)
+# # Raises JSON::GeneratorError (917: Infinity not allowed in JSON):
+# JSON.generate(JSON::Infinity)
+# # Raises JSON::GeneratorError (917: -Infinity not allowed in JSON):
+# JSON.generate(JSON::MinusInfinity)
+#
+# Allow:
+# ruby = [Float::NAN, Float::INFINITY, JSON::NaN, JSON::Infinity, JSON::MinusInfinity]
+# JSON.generate(ruby, allow_nan: true) # => '[NaN,Infinity,NaN,Infinity,-Infinity]'
+#
+# ---
+#
+# Option +allow_duplicate_key+ (boolean) specifies whether
+# hashes with duplicate keys should be allowed or produce an error.
+# defaults to emit a deprecation warning.
+#
+# With the default, (not set):
+# Warning[:deprecated] = true
+# JSON.generate({ foo: 1, "foo" => 2 })
+# # warning: detected duplicate key "foo" in {foo: 1, "foo" => 2}.
+# # This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`
+# # => '{"foo":1,"foo":2}'
+#
+# With <tt>false</tt>
+# JSON.generate({ foo: 1, "foo" => 2 }, allow_duplicate_key: false)
+# # detected duplicate key "foo" in {foo: 1, "foo" => 2} (JSON::GeneratorError)
+#
+# In version 3.0, <tt>false</tt> will become the default.
+#
+# ---
+#
+# Option +max_nesting+ (\Integer) specifies the maximum nesting depth
+# in +obj+; defaults to +100+.
+#
+# With the default, +100+:
+# obj = [[[[[[0]]]]]]
+# JSON.generate(obj) # => '[[[[[[0]]]]]]'
+#
+# Too deep:
+# # Raises JSON::NestingError (nesting of 2 is too deep):
+# JSON.generate(obj, max_nesting: 2)
+#
+# ====== Escaping Options
+#
+# Options +script_safe+ (boolean) specifies wether <tt>'\u2028'</tt>, <tt>'\u2029'</tt>
+# and <tt>'/'</tt> should be escaped as to make the JSON object safe to interpolate in script
+# tags.
+#
+# Options +ascii_only+ (boolean) specifies wether all characters outside the ASCII range
+# should be escaped.
+#
+# ====== Output Options
+#
+# The default formatting options generate the most compact
+# \JSON data, all on one line and with no whitespace.
+#
+# You can use these formatting options to generate
+# \JSON data in a more open format, using whitespace.
+# See also JSON.pretty_generate.
+#
+# - Option +array_nl+ (\String) specifies a string (usually a newline)
+# to be inserted after each \JSON array; defaults to the empty \String, <tt>''</tt>.
+# - Option +object_nl+ (\String) specifies a string (usually a newline)
+# to be inserted after each \JSON object; defaults to the empty \String, <tt>''</tt>.
+# - Option +indent+ (\String) specifies the string (usually spaces) to be
+# used for indentation; defaults to the empty \String, <tt>''</tt>;
+# defaults to the empty \String, <tt>''</tt>;
+# has no effect unless options +array_nl+ or +object_nl+ specify newlines.
+# - Option +space+ (\String) specifies a string (usually a space) to be
+# inserted after the colon in each \JSON object's pair;
+# defaults to the empty \String, <tt>''</tt>.
+# - Option +space_before+ (\String) specifies a string (usually a space) to be
+# inserted before the colon in each \JSON object's pair;
+# defaults to the empty \String, <tt>''</tt>.
+#
+# In this example, +obj+ is used first to generate the shortest
+# \JSON data (no whitespace), then again with all formatting options
+# specified:
+#
+# obj = {foo: [:bar, :baz], bat: {bam: 0, bad: 1}}
+# json = JSON.generate(obj)
+# puts 'Compact:', json
+# opts = {
+# array_nl: "\n",
+# object_nl: "\n",
+# indent: ' ',
+# space_before: ' ',
+# space: ' '
+# }
+# puts 'Open:', JSON.generate(obj, opts)
+#
+# Output:
+# Compact:
+# {"foo":["bar","baz"],"bat":{"bam":0,"bad":1}}
+# Open:
+# {
+# "foo" : [
+# "bar",
+# "baz"
+# ],
+# "bat" : {
+# "bam" : 0,
+# "bad" : 1
+# }
+# }
+#
+# == \JSON Additions
+#
+# Note that JSON Additions must only be used with trusted data, and is
+# deprecated.
+#
+# When you "round trip" a non-\String object from Ruby to \JSON and back,
+# you have a new \String, instead of the object you began with:
+# ruby0 = Range.new(0, 2)
+# json = JSON.generate(ruby0)
+# json # => '0..2"'
+# ruby1 = JSON.parse(json)
+# ruby1 # => '0..2'
+# ruby1.class # => String
+#
+# You can use \JSON _additions_ to preserve the original object.
+# The addition is an extension of a ruby class, so that:
+# - \JSON.generate stores more information in the \JSON string.
+# - \JSON.parse, called with option +create_additions+,
+# uses that information to create a proper Ruby object.
+#
+# This example shows a \Range being generated into \JSON
+# and parsed back into Ruby, both without and with
+# the addition for \Range:
+# ruby = Range.new(0, 2)
+# # This passage does not use the addition for Range.
+# json0 = JSON.generate(ruby)
+# ruby0 = JSON.parse(json0)
+# # This passage uses the addition for Range.
+# require 'json/add/range'
+# json1 = JSON.generate(ruby)
+# ruby1 = JSON.parse(json1, create_additions: true)
+# # Make a nice display.
+# display = <<~EOT
+# Generated JSON:
+# Without addition: #{json0} (#{json0.class})
+# With addition: #{json1} (#{json1.class})
+# Parsed JSON:
+# Without addition: #{ruby0.inspect} (#{ruby0.class})
+# With addition: #{ruby1.inspect} (#{ruby1.class})
+# EOT
+# puts display
+#
+# This output shows the different results:
+# Generated JSON:
+# Without addition: "0..2" (String)
+# With addition: {"json_class":"Range","a":[0,2,false]} (String)
+# Parsed JSON:
+# Without addition: "0..2" (String)
+# With addition: 0..2 (Range)
+#
+# The \JSON module includes additions for certain classes.
+# You can also craft custom additions.
+# See {Custom \JSON Additions}[#module-JSON-label-Custom+JSON+Additions].
+#
+# === Built-in Additions
+#
+# The \JSON module includes additions for certain classes.
+# To use an addition, +require+ its source:
+# - BigDecimal: <tt>require 'json/add/bigdecimal'</tt>
+# - Complex: <tt>require 'json/add/complex'</tt>
+# - Date: <tt>require 'json/add/date'</tt>
+# - DateTime: <tt>require 'json/add/date_time'</tt>
+# - Exception: <tt>require 'json/add/exception'</tt>
+# - OpenStruct: <tt>require 'json/add/ostruct'</tt>
+# - Range: <tt>require 'json/add/range'</tt>
+# - Rational: <tt>require 'json/add/rational'</tt>
+# - Regexp: <tt>require 'json/add/regexp'</tt>
+# - Set: <tt>require 'json/add/set'</tt>
+# - Struct: <tt>require 'json/add/struct'</tt>
+# - Symbol: <tt>require 'json/add/symbol'</tt>
+# - Time: <tt>require 'json/add/time'</tt>
+#
+# To reduce punctuation clutter, the examples below
+# show the generated \JSON via +puts+, rather than the usual +inspect+,
+#
+# \BigDecimal:
+# require 'json/add/bigdecimal'
+# ruby0 = BigDecimal(0) # 0.0
+# json = JSON.generate(ruby0) # {"json_class":"BigDecimal","b":"27:0.0"}
+# ruby1 = JSON.parse(json, create_additions: true) # 0.0
+# ruby1.class # => BigDecimal
+#
+# \Complex:
+# require 'json/add/complex'
+# ruby0 = Complex(1+0i) # 1+0i
+# json = JSON.generate(ruby0) # {"json_class":"Complex","r":1,"i":0}
+# ruby1 = JSON.parse(json, create_additions: true) # 1+0i
+# ruby1.class # Complex
+#
+# \Date:
+# require 'json/add/date'
+# ruby0 = Date.today # 2020-05-02
+# json = JSON.generate(ruby0) # {"json_class":"Date","y":2020,"m":5,"d":2,"sg":2299161.0}
+# ruby1 = JSON.parse(json, create_additions: true) # 2020-05-02
+# ruby1.class # Date
+#
+# \DateTime:
+# require 'json/add/date_time'
+# ruby0 = DateTime.now # 2020-05-02T10:38:13-05:00
+# json = JSON.generate(ruby0) # {"json_class":"DateTime","y":2020,"m":5,"d":2,"H":10,"M":38,"S":13,"of":"-5/24","sg":2299161.0}
+# ruby1 = JSON.parse(json, create_additions: true) # 2020-05-02T10:38:13-05:00
+# ruby1.class # DateTime
+#
+# \Exception (and its subclasses including \RuntimeError):
+# require 'json/add/exception'
+# ruby0 = Exception.new('A message') # A message
+# json = JSON.generate(ruby0) # {"json_class":"Exception","m":"A message","b":null}
+# ruby1 = JSON.parse(json, create_additions: true) # A message
+# ruby1.class # Exception
+# ruby0 = RuntimeError.new('Another message') # Another message
+# json = JSON.generate(ruby0) # {"json_class":"RuntimeError","m":"Another message","b":null}
+# ruby1 = JSON.parse(json, create_additions: true) # Another message
+# ruby1.class # RuntimeError
+#
+# \OpenStruct:
+# require 'json/add/ostruct'
+# ruby0 = OpenStruct.new(name: 'Matz', language: 'Ruby') # #<OpenStruct name="Matz", language="Ruby">
+# json = JSON.generate(ruby0) # {"json_class":"OpenStruct","t":{"name":"Matz","language":"Ruby"}}
+# ruby1 = JSON.parse(json, create_additions: true) # #<OpenStruct name="Matz", language="Ruby">
+# ruby1.class # OpenStruct
+#
+# \Range:
+# require 'json/add/range'
+# ruby0 = Range.new(0, 2) # 0..2
+# json = JSON.generate(ruby0) # {"json_class":"Range","a":[0,2,false]}
+# ruby1 = JSON.parse(json, create_additions: true) # 0..2
+# ruby1.class # Range
+#
+# \Rational:
+# require 'json/add/rational'
+# ruby0 = Rational(1, 3) # 1/3
+# json = JSON.generate(ruby0) # {"json_class":"Rational","n":1,"d":3}
+# ruby1 = JSON.parse(json, create_additions: true) # 1/3
+# ruby1.class # Rational
+#
+# \Regexp:
+# require 'json/add/regexp'
+# ruby0 = Regexp.new('foo') # (?-mix:foo)
+# json = JSON.generate(ruby0) # {"json_class":"Regexp","o":0,"s":"foo"}
+# ruby1 = JSON.parse(json, create_additions: true) # (?-mix:foo)
+# ruby1.class # Regexp
+#
+# \Set:
+# require 'json/add/set'
+# ruby0 = Set.new([0, 1, 2]) # #<Set: {0, 1, 2}>
+# json = JSON.generate(ruby0) # {"json_class":"Set","a":[0,1,2]}
+# ruby1 = JSON.parse(json, create_additions: true) # #<Set: {0, 1, 2}>
+# ruby1.class # Set
+#
+# \Struct:
+# require 'json/add/struct'
+# Customer = Struct.new(:name, :address) # Customer
+# ruby0 = Customer.new("Dave", "123 Main") # #<struct Customer name="Dave", address="123 Main">
+# json = JSON.generate(ruby0) # {"json_class":"Customer","v":["Dave","123 Main"]}
+# ruby1 = JSON.parse(json, create_additions: true) # #<struct Customer name="Dave", address="123 Main">
+# ruby1.class # Customer
+#
+# \Symbol:
+# require 'json/add/symbol'
+# ruby0 = :foo # foo
+# json = JSON.generate(ruby0) # {"json_class":"Symbol","s":"foo"}
+# ruby1 = JSON.parse(json, create_additions: true) # foo
+# ruby1.class # Symbol
+#
+# \Time:
+# require 'json/add/time'
+# ruby0 = Time.now # 2020-05-02 11:28:26 -0500
+# json = JSON.generate(ruby0) # {"json_class":"Time","s":1588436906,"n":840560000}
+# ruby1 = JSON.parse(json, create_additions: true) # 2020-05-02 11:28:26 -0500
+# ruby1.class # Time
+#
+#
+# === Custom \JSON Additions
+#
+# In addition to the \JSON additions provided,
+# you can craft \JSON additions of your own,
+# either for Ruby built-in classes or for user-defined classes.
+#
+# Here's a user-defined class +Foo+:
+# class Foo
+# attr_accessor :bar, :baz
+# def initialize(bar, baz)
+# self.bar = bar
+# self.baz = baz
+# end
+# end
+#
+# Here's the \JSON addition for it:
+# # Extend class Foo with JSON addition.
+# class Foo
+# # Serialize Foo object with its class name and arguments
+# def to_json(*args)
+# {
+# JSON.create_id => self.class.name,
+# 'a' => [ bar, baz ]
+# }.to_json(*args)
+# end
+# # Deserialize JSON string by constructing new Foo object with arguments.
+# def self.json_create(object)
+# new(*object['a'])
+# end
+# end
+#
+# Demonstration:
+# require 'json'
+# # This Foo object has no custom addition.
+# foo0 = Foo.new(0, 1)
+# json0 = JSON.generate(foo0)
+# obj0 = JSON.parse(json0)
+# # Lood the custom addition.
+# require_relative 'foo_addition'
+# # This foo has the custom addition.
+# foo1 = Foo.new(0, 1)
+# json1 = JSON.generate(foo1)
+# obj1 = JSON.parse(json1, create_additions: true)
+# # Make a nice display.
+# display = <<~EOT
+# Generated JSON:
+# Without custom addition: #{json0} (#{json0.class})
+# With custom addition: #{json1} (#{json1.class})
+# Parsed JSON:
+# Without custom addition: #{obj0.inspect} (#{obj0.class})
+# With custom addition: #{obj1.inspect} (#{obj1.class})
+# EOT
+# puts display
+#
+# Output:
+#
+# Generated JSON:
+# Without custom addition: "#<Foo:0x0000000006534e80>" (String)
+# With custom addition: {"json_class":"Foo","a":[0,1]} (String)
+# Parsed JSON:
+# Without custom addition: "#<Foo:0x0000000006534e80>" (String)
+# With custom addition: #<Foo:0x0000000006473bb8 @bar=0, @baz=1> (Foo)
#
module JSON
require 'json/version'
-
- if VARIANT_BINARY
- require 'json/ext'
- else
- begin
- require 'json/ext'
- rescue LoadError
- require 'json/pure'
- end
- end
-
- JSON_LOADED = true
+ require 'json/ext'
end
diff --git a/ext/json/lib/json/Array.xpm b/ext/json/lib/json/Array.xpm
deleted file mode 100644
index 27c48011f9..0000000000
--- a/ext/json/lib/json/Array.xpm
+++ /dev/null
@@ -1,21 +0,0 @@
-/* XPM */
-static char * Array_xpm[] = {
-"16 16 2 1",
-" c None",
-". c #000000",
-" ",
-" ",
-" ",
-" .......... ",
-" . . ",
-" . . ",
-" . . ",
-" . . ",
-" . . ",
-" . . ",
-" . . ",
-" . . ",
-" .......... ",
-" ",
-" ",
-" "};
diff --git a/ext/json/lib/json/FalseClass.xpm b/ext/json/lib/json/FalseClass.xpm
deleted file mode 100644
index 25ce60832d..0000000000
--- a/ext/json/lib/json/FalseClass.xpm
+++ /dev/null
@@ -1,21 +0,0 @@
-/* XPM */
-static char * False_xpm[] = {
-"16 16 2 1",
-" c None",
-". c #FF0000",
-" ",
-" ",
-" ",
-" ...... ",
-" . ",
-" . ",
-" . ",
-" ...... ",
-" . ",
-" . ",
-" . ",
-" . ",
-" . ",
-" ",
-" ",
-" "};
diff --git a/ext/json/lib/json/Hash.xpm b/ext/json/lib/json/Hash.xpm
deleted file mode 100644
index cd8f6f7b53..0000000000
--- a/ext/json/lib/json/Hash.xpm
+++ /dev/null
@@ -1,21 +0,0 @@
-/* XPM */
-static char * Hash_xpm[] = {
-"16 16 2 1",
-" c None",
-". c #000000",
-" ",
-" ",
-" ",
-" . . ",
-" . . ",
-" . . ",
-" ......... ",
-" . . ",
-" . . ",
-" ......... ",
-" . . ",
-" . . ",
-" . . ",
-" ",
-" ",
-" "};
diff --git a/ext/json/lib/json/Key.xpm b/ext/json/lib/json/Key.xpm
deleted file mode 100644
index 9fd7281388..0000000000
--- a/ext/json/lib/json/Key.xpm
+++ /dev/null
@@ -1,73 +0,0 @@
-/* XPM */
-static char * Key_xpm[] = {
-"16 16 54 1",
-" c None",
-". c #110007",
-"+ c #0E0900",
-"@ c #000013",
-"# c #070600",
-"$ c #F6F006",
-"% c #ECE711",
-"& c #E5EE00",
-"* c #16021E",
-"= c #120900",
-"- c #EDF12B",
-"; c #000033",
-"> c #0F0000",
-", c #FFFE03",
-"' c #E6E500",
-") c #16021B",
-"! c #F7F502",
-"~ c #000E00",
-"{ c #130000",
-"] c #FFF000",
-"^ c #FFE711",
-"/ c #140005",
-"( c #190025",
-"_ c #E9DD27",
-": c #E7DC04",
-"< c #FFEC09",
-"[ c #FFE707",
-"} c #FFDE10",
-"| c #150021",
-"1 c #160700",
-"2 c #FAF60E",
-"3 c #EFE301",
-"4 c #FEF300",
-"5 c #E7E000",
-"6 c #FFFF08",
-"7 c #0E0206",
-"8 c #040000",
-"9 c #03052E",
-"0 c #041212",
-"a c #070300",
-"b c #F2E713",
-"c c #F9DE13",
-"d c #36091E",
-"e c #00001C",
-"f c #1F0010",
-"g c #FFF500",
-"h c #DEDE00",
-"i c #050A00",
-"j c #FAF14A",
-"k c #F5F200",
-"l c #040404",
-"m c #1A0D00",
-"n c #EDE43D",
-"o c #ECE007",
-" ",
-" ",
-" .+@ ",
-" #$%&* ",
-" =-;>,') ",
-" >!~{]^/ ",
-" (_:<[}| ",
-" 1234567 ",
-" 890abcd ",
-" efghi ",
-" >jkl ",
-" mnol ",
-" >kl ",
-" ll ",
-" ",
-" "};
diff --git a/ext/json/lib/json/NilClass.xpm b/ext/json/lib/json/NilClass.xpm
deleted file mode 100644
index 3509f06c99..0000000000
--- a/ext/json/lib/json/NilClass.xpm
+++ /dev/null
@@ -1,21 +0,0 @@
-/* XPM */
-static char * False_xpm[] = {
-"16 16 2 1",
-" c None",
-". c #000000",
-" ",
-" ",
-" ",
-" ... ",
-" . . ",
-" . . ",
-" . . ",
-" . . ",
-" . . ",
-" . . ",
-" . . ",
-" . . ",
-" ... ",
-" ",
-" ",
-" "};
diff --git a/ext/json/lib/json/Numeric.xpm b/ext/json/lib/json/Numeric.xpm
deleted file mode 100644
index e071e2ee9c..0000000000
--- a/ext/json/lib/json/Numeric.xpm
+++ /dev/null
@@ -1,28 +0,0 @@
-/* XPM */
-static char * Numeric_xpm[] = {
-"16 16 9 1",
-" c None",
-". c #FF0000",
-"+ c #0000FF",
-"@ c #0023DB",
-"# c #00EA14",
-"$ c #00FF00",
-"% c #004FAF",
-"& c #0028D6",
-"* c #00F20C",
-" ",
-" ",
-" ",
-" ... +++@#$$$$ ",
-" .+ %& $$ ",
-" . + $ ",
-" . + $$ ",
-" . ++$$$$ ",
-" . + $$ ",
-" . + $ ",
-" . + $ ",
-" . + $ $$ ",
-" .....++++*$$ ",
-" ",
-" ",
-" "};
diff --git a/ext/json/lib/json/String.xpm b/ext/json/lib/json/String.xpm
deleted file mode 100644
index f79a89cdc1..0000000000
--- a/ext/json/lib/json/String.xpm
+++ /dev/null
@@ -1,96 +0,0 @@
-/* XPM */
-static char * String_xpm[] = {
-"16 16 77 1",
-" c None",
-". c #000000",
-"+ c #040404",
-"@ c #080806",
-"# c #090606",
-"$ c #EEEAE1",
-"% c #E7E3DA",
-"& c #E0DBD1",
-"* c #D4B46F",
-"= c #0C0906",
-"- c #E3C072",
-"; c #E4C072",
-"> c #060505",
-", c #0B0A08",
-"' c #D5B264",
-") c #D3AF5A",
-"! c #080602",
-"~ c #E1B863",
-"{ c #DDB151",
-"] c #DBAE4A",
-"^ c #DDB152",
-"/ c #DDB252",
-"( c #070705",
-"_ c #0C0A07",
-": c #D3A33B",
-"< c #020201",
-"[ c #DAAA41",
-"} c #040302",
-"| c #E4D9BF",
-"1 c #0B0907",
-"2 c #030201",
-"3 c #020200",
-"4 c #C99115",
-"5 c #080704",
-"6 c #DBC8A2",
-"7 c #E7D7B4",
-"8 c #E0CD9E",
-"9 c #080601",
-"0 c #040400",
-"a c #010100",
-"b c #0B0B08",
-"c c #DCBF83",
-"d c #DCBC75",
-"e c #DEB559",
-"f c #040301",
-"g c #BC8815",
-"h c #120E07",
-"i c #060402",
-"j c #0A0804",
-"k c #D4A747",
-"l c #D6A12F",
-"m c #0E0C05",
-"n c #C8C1B0",
-"o c #1D1B15",
-"p c #D7AD51",
-"q c #070502",
-"r c #080804",
-"s c #BC953B",
-"t c #C4BDAD",
-"u c #0B0807",
-"v c #DBAC47",
-"w c #1B150A",
-"x c #B78A2C",
-"y c #D8A83C",
-"z c #D4A338",
-"A c #0F0B03",
-"B c #181105",
-"C c #C59325",
-"D c #C18E1F",
-"E c #060600",
-"F c #CC992D",
-"G c #B98B25",
-"H c #B3831F",
-"I c #C08C1C",
-"J c #060500",
-"K c #0E0C03",
-"L c #0D0A00",
-" ",
-" .+@# ",
-" .$%&*= ",
-" .-;>,')! ",
-" .~. .{]. ",
-" .^/. (_:< ",
-" .[.}|$12 ",
-" 345678}90 ",
-" a2bcdefgh ",
-" ijkl.mno ",
-" <pq. rstu ",
-" .]v. wx= ",
-" .yzABCDE ",
-" .FGHIJ ",
-" 0KL0 ",
-" "};
diff --git a/ext/json/lib/json/TrueClass.xpm b/ext/json/lib/json/TrueClass.xpm
deleted file mode 100644
index 143eef49bb..0000000000
--- a/ext/json/lib/json/TrueClass.xpm
+++ /dev/null
@@ -1,21 +0,0 @@
-/* XPM */
-static char * TrueClass_xpm[] = {
-"16 16 2 1",
-" c None",
-". c #0BF311",
-" ",
-" ",
-" ",
-" ......... ",
-" . ",
-" . ",
-" . ",
-" . ",
-" . ",
-" . ",
-" . ",
-" . ",
-" . ",
-" ",
-" ",
-" "};
diff --git a/ext/json/lib/json/add/bigdecimal.rb b/ext/json/lib/json/add/bigdecimal.rb
new file mode 100644
index 0000000000..dc84572f31
--- /dev/null
+++ b/ext/json/lib/json/add/bigdecimal.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
+ require 'json'
+end
+begin
+ require 'bigdecimal'
+rescue LoadError
+end
+
+class BigDecimal
+
+ # See #as_json.
+ def self.json_create(object)
+ BigDecimal._load object['b']
+ end
+
+ # Methods <tt>BigDecimal#as_json</tt> and +BigDecimal.json_create+ may be used
+ # to serialize and deserialize a \BigDecimal object;
+ # see Marshal[rdoc-ref:Marshal].
+ #
+ # \Method <tt>BigDecimal#as_json</tt> serializes +self+,
+ # returning a 2-element hash representing +self+:
+ #
+ # require 'json/add/bigdecimal'
+ # x = BigDecimal(2).as_json # => {"json_class"=>"BigDecimal", "b"=>"27:0.2e1"}
+ # y = BigDecimal(2.0, 4).as_json # => {"json_class"=>"BigDecimal", "b"=>"36:0.2e1"}
+ # z = BigDecimal(Complex(2, 0)).as_json # => {"json_class"=>"BigDecimal", "b"=>"27:0.2e1"}
+ #
+ # \Method +JSON.create+ deserializes such a hash, returning a \BigDecimal object:
+ #
+ # BigDecimal.json_create(x) # => 0.2e1
+ # BigDecimal.json_create(y) # => 0.2e1
+ # BigDecimal.json_create(z) # => 0.2e1
+ #
+ def as_json(*)
+ {
+ JSON.create_id => self.class.name,
+ 'b' => _dump.force_encoding(Encoding::UTF_8),
+ }
+ end
+
+ # Returns a JSON string representing +self+:
+ #
+ # require 'json/add/bigdecimal'
+ # puts BigDecimal(2).to_json
+ # puts BigDecimal(2.0, 4).to_json
+ # puts BigDecimal(Complex(2, 0)).to_json
+ #
+ # Output:
+ #
+ # {"json_class":"BigDecimal","b":"27:0.2e1"}
+ # {"json_class":"BigDecimal","b":"36:0.2e1"}
+ # {"json_class":"BigDecimal","b":"27:0.2e1"}
+ #
+ def to_json(*args)
+ as_json.to_json(*args)
+ end
+end if defined?(::BigDecimal)
diff --git a/ext/json/lib/json/add/complex.rb b/ext/json/lib/json/add/complex.rb
new file mode 100644
index 0000000000..9e3c6f2d0a
--- /dev/null
+++ b/ext/json/lib/json/add/complex.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
+ require 'json'
+end
+
+class Complex
+
+ # See #as_json.
+ def self.json_create(object)
+ Complex(object['r'], object['i'])
+ end
+
+ # Methods <tt>Complex#as_json</tt> and +Complex.json_create+ may be used
+ # to serialize and deserialize a \Complex object;
+ # see Marshal[rdoc-ref:Marshal].
+ #
+ # \Method <tt>Complex#as_json</tt> serializes +self+,
+ # returning a 2-element hash representing +self+:
+ #
+ # require 'json/add/complex'
+ # x = Complex(2).as_json # => {"json_class"=>"Complex", "r"=>2, "i"=>0}
+ # y = Complex(2.0, 4).as_json # => {"json_class"=>"Complex", "r"=>2.0, "i"=>4}
+ #
+ # \Method +JSON.create+ deserializes such a hash, returning a \Complex object:
+ #
+ # Complex.json_create(x) # => (2+0i)
+ # Complex.json_create(y) # => (2.0+4i)
+ #
+ def as_json(*)
+ {
+ JSON.create_id => self.class.name,
+ 'r' => real,
+ 'i' => imag,
+ }
+ end
+
+ # Returns a JSON string representing +self+:
+ #
+ # require 'json/add/complex'
+ # puts Complex(2).to_json
+ # puts Complex(2.0, 4).to_json
+ #
+ # Output:
+ #
+ # {"json_class":"Complex","r":2,"i":0}
+ # {"json_class":"Complex","r":2.0,"i":4}
+ #
+ def to_json(*args)
+ as_json.to_json(*args)
+ end
+end
diff --git a/ext/json/lib/json/add/core.rb b/ext/json/lib/json/add/core.rb
index 630ed6e4b7..61ff454212 100644
--- a/ext/json/lib/json/add/core.rb
+++ b/ext/json/lib/json/add/core.rb
@@ -1,120 +1,13 @@
-# This file contains implementations of ruby core's custom objects for
+# frozen_string_literal: true
+# This file requires the implementations of ruby core's custom objects for
# serialisation/deserialisation.
-unless Object.const_defined?(:JSON) and ::JSON.const_defined?(:JSON_LOADED) and
- ::JSON::JSON_LOADED
- require 'json'
-end
-require 'date'
-
-class Time
- def self.json_create(object)
- at(*object.values_at('s', 'u'))
- end
-
- def to_json(*args)
- {
- 'json_class' => self.class.name.to_s,
- 's' => tv_sec,
- 'u' => tv_usec,
- }.to_json(*args)
- end
-end
-
-class Date
- def self.json_create(object)
- civil(*object.values_at('y', 'm', 'd', 'sg'))
- end
-
- def to_json(*args)
- {
- 'json_class' => self.class.name.to_s,
- 'y' => year,
- 'm' => month,
- 'd' => day,
- 'sg' => @sg,
- }.to_json(*args)
- end
-end
-
-class DateTime
- def self.json_create(object)
- args = object.values_at('y', 'm', 'd', 'H', 'M', 'S')
- of_a, of_b = object['of'].split('/')
- args << Rational(of_a.to_i, of_b.to_i)
- args << object['sg']
- civil(*args)
- end
-
- def to_json(*args)
- {
- 'json_class' => self.class.name.to_s,
- 'y' => year,
- 'm' => month,
- 'd' => day,
- 'H' => hour,
- 'M' => min,
- 'S' => sec,
- 'of' => offset.to_s,
- 'sg' => @sg,
- }.to_json(*args)
- end
-end
-
-class Range
- def self.json_create(object)
- new(*object['a'])
- end
-
- def to_json(*args)
- {
- 'json_class' => self.class.name.to_s,
- 'a' => [ first, last, exclude_end? ]
- }.to_json(*args)
- end
-end
-
-class Struct
- def self.json_create(object)
- new(*object['v'])
- end
-
- def to_json(*args)
- klass = self.class.name.to_s
- klass.empty? and raise JSON::JSONError, "Only named structs are supported!"
- {
- 'json_class' => klass,
- 'v' => values,
- }.to_json(*args)
- end
-end
-
-class Exception
- def self.json_create(object)
- result = new(object['m'])
- result.set_backtrace object['b']
- result
- end
-
- def to_json(*args)
- {
- 'json_class' => self.class.name.to_s,
- 'm' => message,
- 'b' => backtrace,
- }.to_json(*args)
- end
-end
-
-class Regexp
- def self.json_create(object)
- new(object['s'], object['o'])
- end
-
- def to_json(*)
- {
- 'json_class' => self.class.name.to_s,
- 'o' => options,
- 's' => source,
- }.to_json
- end
-end
+require 'json/add/date'
+require 'json/add/date_time'
+require 'json/add/exception'
+require 'json/add/range'
+require 'json/add/regexp'
+require 'json/add/string'
+require 'json/add/struct'
+require 'json/add/symbol'
+require 'json/add/time'
diff --git a/ext/json/lib/json/add/date.rb b/ext/json/lib/json/add/date.rb
new file mode 100644
index 0000000000..88a098b637
--- /dev/null
+++ b/ext/json/lib/json/add/date.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
+ require 'json'
+end
+require 'date'
+
+class Date
+
+ # See #as_json.
+ def self.json_create(object)
+ civil(*object.values_at('y', 'm', 'd', 'sg'))
+ end
+
+ alias start sg unless method_defined?(:start)
+
+ # Methods <tt>Date#as_json</tt> and +Date.json_create+ may be used
+ # to serialize and deserialize a \Date object;
+ # see Marshal[rdoc-ref:Marshal].
+ #
+ # \Method <tt>Date#as_json</tt> serializes +self+,
+ # returning a 2-element hash representing +self+:
+ #
+ # require 'json/add/date'
+ # x = Date.today.as_json
+ # # => {"json_class"=>"Date", "y"=>2023, "m"=>11, "d"=>21, "sg"=>2299161.0}
+ #
+ # \Method +JSON.create+ deserializes such a hash, returning a \Date object:
+ #
+ # Date.json_create(x)
+ # # => #<Date: 2023-11-21 ((2460270j,0s,0n),+0s,2299161j)>
+ #
+ def as_json(*)
+ {
+ JSON.create_id => self.class.name,
+ 'y' => year,
+ 'm' => month,
+ 'd' => day,
+ 'sg' => start,
+ }
+ end
+
+ # Returns a JSON string representing +self+:
+ #
+ # require 'json/add/date'
+ # puts Date.today.to_json
+ #
+ # Output:
+ #
+ # {"json_class":"Date","y":2023,"m":11,"d":21,"sg":2299161.0}
+ #
+ def to_json(*args)
+ as_json.to_json(*args)
+ end
+end
diff --git a/ext/json/lib/json/add/date_time.rb b/ext/json/lib/json/add/date_time.rb
new file mode 100644
index 0000000000..8b0bb5d181
--- /dev/null
+++ b/ext/json/lib/json/add/date_time.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
+ require 'json'
+end
+require 'date'
+
+class DateTime
+
+ # See #as_json.
+ def self.json_create(object)
+ args = object.values_at('y', 'm', 'd', 'H', 'M', 'S')
+ of_a, of_b = object['of'].split('/')
+ if of_b and of_b != '0'
+ args << Rational(of_a.to_i, of_b.to_i)
+ else
+ args << of_a
+ end
+ args << object['sg']
+ civil(*args)
+ end
+
+ alias start sg unless method_defined?(:start)
+
+ # Methods <tt>DateTime#as_json</tt> and +DateTime.json_create+ may be used
+ # to serialize and deserialize a \DateTime object;
+ # see Marshal[rdoc-ref:Marshal].
+ #
+ # \Method <tt>DateTime#as_json</tt> serializes +self+,
+ # returning a 2-element hash representing +self+:
+ #
+ # require 'json/add/datetime'
+ # x = DateTime.now.as_json
+ # # => {"json_class"=>"DateTime", "y"=>2023, "m"=>11, "d"=>21, "sg"=>2299161.0}
+ #
+ # \Method +JSON.create+ deserializes such a hash, returning a \DateTime object:
+ #
+ # DateTime.json_create(x) # BUG? Raises Date::Error "invalid date"
+ #
+ def as_json(*)
+ {
+ JSON.create_id => self.class.name,
+ 'y' => year,
+ 'm' => month,
+ 'd' => day,
+ 'H' => hour,
+ 'M' => min,
+ 'S' => sec,
+ 'of' => offset.to_s,
+ 'sg' => start,
+ }
+ end
+
+ # Returns a JSON string representing +self+:
+ #
+ # require 'json/add/datetime'
+ # puts DateTime.now.to_json
+ #
+ # Output:
+ #
+ # {"json_class":"DateTime","y":2023,"m":11,"d":21,"sg":2299161.0}
+ #
+ def to_json(*args)
+ as_json.to_json(*args)
+ end
+end
+
+
diff --git a/ext/json/lib/json/add/exception.rb b/ext/json/lib/json/add/exception.rb
new file mode 100644
index 0000000000..e85d404982
--- /dev/null
+++ b/ext/json/lib/json/add/exception.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
+ require 'json'
+end
+
+class Exception
+
+ # See #as_json.
+ def self.json_create(object)
+ result = new(object['m'])
+ result.set_backtrace object['b']
+ result
+ end
+
+ # Methods <tt>Exception#as_json</tt> and +Exception.json_create+ may be used
+ # to serialize and deserialize a \Exception object;
+ # see Marshal[rdoc-ref:Marshal].
+ #
+ # \Method <tt>Exception#as_json</tt> serializes +self+,
+ # returning a 2-element hash representing +self+:
+ #
+ # require 'json/add/exception'
+ # x = Exception.new('Foo').as_json # => {"json_class"=>"Exception", "m"=>"Foo", "b"=>nil}
+ #
+ # \Method +JSON.create+ deserializes such a hash, returning a \Exception object:
+ #
+ # Exception.json_create(x) # => #<Exception: Foo>
+ #
+ def as_json(*)
+ {
+ JSON.create_id => self.class.name,
+ 'm' => message,
+ 'b' => backtrace,
+ }
+ end
+
+ # Returns a JSON string representing +self+:
+ #
+ # require 'json/add/exception'
+ # puts Exception.new('Foo').to_json
+ #
+ # Output:
+ #
+ # {"json_class":"Exception","m":"Foo","b":null}
+ #
+ def to_json(*args)
+ as_json.to_json(*args)
+ end
+end
diff --git a/ext/json/lib/json/add/ostruct.rb b/ext/json/lib/json/add/ostruct.rb
new file mode 100644
index 0000000000..7750498144
--- /dev/null
+++ b/ext/json/lib/json/add/ostruct.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
+ require 'json'
+end
+begin
+ require 'ostruct'
+rescue LoadError
+end
+
+class OpenStruct
+
+ # See #as_json.
+ def self.json_create(object)
+ new(object['t'] || object[:t])
+ end
+
+ # Methods <tt>OpenStruct#as_json</tt> and +OpenStruct.json_create+ may be used
+ # to serialize and deserialize a \OpenStruct object;
+ # see Marshal[rdoc-ref:Marshal].
+ #
+ # \Method <tt>OpenStruct#as_json</tt> serializes +self+,
+ # returning a 2-element hash representing +self+:
+ #
+ # require 'json/add/ostruct'
+ # x = OpenStruct.new('name' => 'Rowdy', :age => nil).as_json
+ # # => {"json_class"=>"OpenStruct", "t"=>{:name=>'Rowdy', :age=>nil}}
+ #
+ # \Method +JSON.create+ deserializes such a hash, returning a \OpenStruct object:
+ #
+ # OpenStruct.json_create(x)
+ # # => #<OpenStruct name='Rowdy', age=nil>
+ #
+ def as_json(*)
+ klass = self.class.name
+ klass.to_s.empty? and raise JSON::JSONError, "Only named structs are supported!"
+ {
+ JSON.create_id => klass,
+ 't' => table,
+ }
+ end
+
+ # Returns a JSON string representing +self+:
+ #
+ # require 'json/add/ostruct'
+ # puts OpenStruct.new('name' => 'Rowdy', :age => nil).to_json
+ #
+ # Output:
+ #
+ # {"json_class":"OpenStruct","t":{'name':'Rowdy',"age":null}}
+ #
+ def to_json(*args)
+ as_json.to_json(*args)
+ end
+end if defined?(::OpenStruct)
diff --git a/ext/json/lib/json/add/rails.rb b/ext/json/lib/json/add/rails.rb
deleted file mode 100644
index e86ed1aab9..0000000000
--- a/ext/json/lib/json/add/rails.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-# This file contains implementations of rails custom objects for
-# serialisation/deserialisation.
-
-unless Object.const_defined?(:JSON) and ::JSON.const_defined?(:JSON_LOADED) and
- ::JSON::JSON_LOADED
- require 'json'
-end
-
-class Object
- def self.json_create(object)
- obj = new
- for key, value in object
- next if key == 'json_class'
- instance_variable_set "@#{key}", value
- end
- obj
- end
-
- def to_json(*a)
- result = {
- 'json_class' => self.class.name
- }
- instance_variables.inject(result) do |r, name|
- r[name[1..-1]] = instance_variable_get name
- r
- end
- result.to_json(*a)
- end
-end
-
-class Symbol
- def to_json(*a)
- to_s.to_json(*a)
- end
-end
-
-module Enumerable
- def to_json(*a)
- to_a.to_json(*a)
- end
-end
-
-# class Regexp
-# def to_json(*)
-# inspect
-# end
-# end
-#
-# The above rails definition has some problems:
-#
-# 1. { 'foo' => /bar/ }.to_json # => "{foo: /bar/}"
-# This isn't valid JSON, because the regular expression syntax is not
-# defined in RFC 4627. (And unquoted strings are disallowed there, too.)
-# Though it is valid Javascript.
-#
-# 2. { 'foo' => /bar/mix }.to_json # => "{foo: /bar/mix}"
-# This isn't even valid Javascript.
-
diff --git a/ext/json/lib/json/add/range.rb b/ext/json/lib/json/add/range.rb
new file mode 100644
index 0000000000..408d2c32f6
--- /dev/null
+++ b/ext/json/lib/json/add/range.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
+ require 'json'
+end
+
+class Range
+
+ # See #as_json.
+ def self.json_create(object)
+ new(*object['a'])
+ end
+
+ # Methods <tt>Range#as_json</tt> and +Range.json_create+ may be used
+ # to serialize and deserialize a \Range object;
+ # see Marshal[rdoc-ref:Marshal].
+ #
+ # \Method <tt>Range#as_json</tt> serializes +self+,
+ # returning a 2-element hash representing +self+:
+ #
+ # require 'json/add/range'
+ # x = (1..4).as_json # => {"json_class"=>"Range", "a"=>[1, 4, false]}
+ # y = (1...4).as_json # => {"json_class"=>"Range", "a"=>[1, 4, true]}
+ # z = ('a'..'d').as_json # => {"json_class"=>"Range", "a"=>["a", "d", false]}
+ #
+ # \Method +JSON.create+ deserializes such a hash, returning a \Range object:
+ #
+ # Range.json_create(x) # => 1..4
+ # Range.json_create(y) # => 1...4
+ # Range.json_create(z) # => "a".."d"
+ #
+ def as_json(*)
+ {
+ JSON.create_id => self.class.name,
+ 'a' => [ first, last, exclude_end? ]
+ }
+ end
+
+ # Returns a JSON string representing +self+:
+ #
+ # require 'json/add/range'
+ # puts (1..4).to_json
+ # puts (1...4).to_json
+ # puts ('a'..'d').to_json
+ #
+ # Output:
+ #
+ # {"json_class":"Range","a":[1,4,false]}
+ # {"json_class":"Range","a":[1,4,true]}
+ # {"json_class":"Range","a":["a","d",false]}
+ #
+ def to_json(*args)
+ as_json.to_json(*args)
+ end
+end
diff --git a/ext/json/lib/json/add/rational.rb b/ext/json/lib/json/add/rational.rb
new file mode 100644
index 0000000000..c95812ea8e
--- /dev/null
+++ b/ext/json/lib/json/add/rational.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
+ require 'json'
+end
+
+class Rational
+
+ # See #as_json.
+ def self.json_create(object)
+ Rational(object['n'], object['d'])
+ end
+
+ # Methods <tt>Rational#as_json</tt> and +Rational.json_create+ may be used
+ # to serialize and deserialize a \Rational object;
+ # see Marshal[rdoc-ref:Marshal].
+ #
+ # \Method <tt>Rational#as_json</tt> serializes +self+,
+ # returning a 2-element hash representing +self+:
+ #
+ # require 'json/add/rational'
+ # x = Rational(2, 3).as_json
+ # # => {"json_class"=>"Rational", "n"=>2, "d"=>3}
+ #
+ # \Method +JSON.create+ deserializes such a hash, returning a \Rational object:
+ #
+ # Rational.json_create(x)
+ # # => (2/3)
+ #
+ def as_json(*)
+ {
+ JSON.create_id => self.class.name,
+ 'n' => numerator,
+ 'd' => denominator,
+ }
+ end
+
+ # Returns a JSON string representing +self+:
+ #
+ # require 'json/add/rational'
+ # puts Rational(2, 3).to_json
+ #
+ # Output:
+ #
+ # {"json_class":"Rational","n":2,"d":3}
+ #
+ def to_json(*args)
+ as_json.to_json(*args)
+ end
+end
diff --git a/ext/json/lib/json/add/regexp.rb b/ext/json/lib/json/add/regexp.rb
new file mode 100644
index 0000000000..aebfb2db5c
--- /dev/null
+++ b/ext/json/lib/json/add/regexp.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
+ require 'json'
+end
+
+class Regexp
+
+ # See #as_json.
+ def self.json_create(object)
+ new(object['s'], object['o'])
+ end
+
+ # Methods <tt>Regexp#as_json</tt> and +Regexp.json_create+ may be used
+ # to serialize and deserialize a \Regexp object;
+ # see Marshal[rdoc-ref:Marshal].
+ #
+ # \Method <tt>Regexp#as_json</tt> serializes +self+,
+ # returning a 2-element hash representing +self+:
+ #
+ # require 'json/add/regexp'
+ # x = /foo/.as_json
+ # # => {"json_class"=>"Regexp", "o"=>0, "s"=>"foo"}
+ #
+ # \Method +JSON.create+ deserializes such a hash, returning a \Regexp object:
+ #
+ # Regexp.json_create(x) # => /foo/
+ #
+ def as_json(*)
+ {
+ JSON.create_id => self.class.name,
+ 'o' => options,
+ 's' => source,
+ }
+ end
+
+ # Returns a JSON string representing +self+:
+ #
+ # require 'json/add/regexp'
+ # puts /foo/.to_json
+ #
+ # Output:
+ #
+ # {"json_class":"Regexp","o":0,"s":"foo"}
+ #
+ def to_json(*args)
+ as_json.to_json(*args)
+ end
+end
diff --git a/ext/json/lib/json/add/set.rb b/ext/json/lib/json/add/set.rb
new file mode 100644
index 0000000000..1918353187
--- /dev/null
+++ b/ext/json/lib/json/add/set.rb
@@ -0,0 +1,48 @@
+unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
+ require 'json'
+end
+defined?(::Set) or require 'set'
+
+class Set
+
+ # See #as_json.
+ def self.json_create(object)
+ new object['a']
+ end
+
+ # Methods <tt>Set#as_json</tt> and +Set.json_create+ may be used
+ # to serialize and deserialize a \Set object;
+ # see Marshal[rdoc-ref:Marshal].
+ #
+ # \Method <tt>Set#as_json</tt> serializes +self+,
+ # returning a 2-element hash representing +self+:
+ #
+ # require 'json/add/set'
+ # x = Set.new(%w/foo bar baz/).as_json
+ # # => {"json_class"=>"Set", "a"=>["foo", "bar", "baz"]}
+ #
+ # \Method +JSON.create+ deserializes such a hash, returning a \Set object:
+ #
+ # Set.json_create(x) # => #<Set: {"foo", "bar", "baz"}>
+ #
+ def as_json(*)
+ {
+ JSON.create_id => self.class.name,
+ 'a' => to_a,
+ }
+ end
+
+ # Returns a JSON string representing +self+:
+ #
+ # require 'json/add/set'
+ # puts Set.new(%w/foo bar baz/).to_json
+ #
+ # Output:
+ #
+ # {"json_class":"Set","a":["foo","bar","baz"]}
+ #
+ def to_json(*args)
+ as_json.to_json(*args)
+ end
+end
+
diff --git a/ext/json/lib/json/add/string.rb b/ext/json/lib/json/add/string.rb
new file mode 100644
index 0000000000..9c3bde27fb
--- /dev/null
+++ b/ext/json/lib/json/add/string.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
+ require 'json'
+end
+
+class String
+ # call-seq: json_create(o)
+ #
+ # Raw Strings are JSON Objects (the raw bytes are stored in an array for the
+ # key "raw"). The Ruby String can be created by this class method.
+ def self.json_create(object)
+ object["raw"].pack("C*")
+ end
+
+ # call-seq: to_json_raw_object()
+ #
+ # This method creates a raw object hash, that can be nested into
+ # other data structures and will be generated as a raw string. This
+ # method should be used, if you want to convert raw strings to JSON
+ # instead of UTF-8 strings, e. g. binary data.
+ def to_json_raw_object
+ {
+ JSON.create_id => self.class.name,
+ "raw" => unpack("C*"),
+ }
+ end
+
+ # call-seq: to_json_raw(*args)
+ #
+ # This method creates a JSON text from the result of a call to
+ # to_json_raw_object of this String.
+ def to_json_raw(...)
+ to_json_raw_object.to_json(...)
+ end
+end
diff --git a/ext/json/lib/json/add/struct.rb b/ext/json/lib/json/add/struct.rb
new file mode 100644
index 0000000000..6760c3d86c
--- /dev/null
+++ b/ext/json/lib/json/add/struct.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
+ require 'json'
+end
+
+class Struct
+
+ # See #as_json.
+ def self.json_create(object)
+ new(*object['v'])
+ end
+
+ # Methods <tt>Struct#as_json</tt> and +Struct.json_create+ may be used
+ # to serialize and deserialize a \Struct object;
+ # see Marshal[rdoc-ref:Marshal].
+ #
+ # \Method <tt>Struct#as_json</tt> serializes +self+,
+ # returning a 2-element hash representing +self+:
+ #
+ # require 'json/add/struct'
+ # Customer = Struct.new('Customer', :name, :address, :zip)
+ # x = Struct::Customer.new.as_json
+ # # => {"json_class"=>"Struct::Customer", "v"=>[nil, nil, nil]}
+ #
+ # \Method +JSON.create+ deserializes such a hash, returning a \Struct object:
+ #
+ # Struct::Customer.json_create(x)
+ # # => #<struct Struct::Customer name=nil, address=nil, zip=nil>
+ #
+ def as_json(*)
+ klass = self.class.name
+ klass.to_s.empty? and raise JSON::JSONError, "Only named structs are supported!"
+ {
+ JSON.create_id => klass,
+ 'v' => values,
+ }
+ end
+
+ # Returns a JSON string representing +self+:
+ #
+ # require 'json/add/struct'
+ # Customer = Struct.new('Customer', :name, :address, :zip)
+ # puts Struct::Customer.new.to_json
+ #
+ # Output:
+ #
+ # {"json_class":"Struct","t":{'name':'Rowdy',"age":null}}
+ #
+ def to_json(*args)
+ as_json.to_json(*args)
+ end
+end
diff --git a/ext/json/lib/json/add/symbol.rb b/ext/json/lib/json/add/symbol.rb
new file mode 100644
index 0000000000..806be4f025
--- /dev/null
+++ b/ext/json/lib/json/add/symbol.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
+ require 'json'
+end
+
+class Symbol
+
+ # Methods <tt>Symbol#as_json</tt> and +Symbol.json_create+ may be used
+ # to serialize and deserialize a \Symbol object;
+ # see Marshal[rdoc-ref:Marshal].
+ #
+ # \Method <tt>Symbol#as_json</tt> serializes +self+,
+ # returning a 2-element hash representing +self+:
+ #
+ # require 'json/add/symbol'
+ # x = :foo.as_json
+ # # => {"json_class"=>"Symbol", "s"=>"foo"}
+ #
+ # \Method +JSON.create+ deserializes such a hash, returning a \Symbol object:
+ #
+ # Symbol.json_create(x) # => :foo
+ #
+ def as_json(*)
+ {
+ JSON.create_id => self.class.name,
+ 's' => to_s,
+ }
+ end
+
+ # Returns a JSON string representing +self+:
+ #
+ # require 'json/add/symbol'
+ # puts :foo.to_json
+ #
+ # Output:
+ #
+ # # {"json_class":"Symbol","s":"foo"}
+ #
+ def to_json(state = nil, *a)
+ state = ::JSON::State.from_state(state)
+ if state.strict?
+ super
+ else
+ as_json.to_json(state, *a)
+ end
+ end
+
+ # See #as_json.
+ def self.json_create(o)
+ o['s'].to_sym
+ end
+end
diff --git a/ext/json/lib/json/add/time.rb b/ext/json/lib/json/add/time.rb
new file mode 100644
index 0000000000..b03d4ff251
--- /dev/null
+++ b/ext/json/lib/json/add/time.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED
+ require 'json'
+end
+
+class Time
+
+ # See #as_json.
+ def self.json_create(object)
+ if usec = object.delete('u') # used to be tv_usec -> tv_nsec
+ object['n'] = usec * 1000
+ end
+ at(object['s'], Rational(object['n'], 1000))
+ end
+
+ # Methods <tt>Time#as_json</tt> and +Time.json_create+ may be used
+ # to serialize and deserialize a \Time object;
+ # see Marshal[rdoc-ref:Marshal].
+ #
+ # \Method <tt>Time#as_json</tt> serializes +self+,
+ # returning a 2-element hash representing +self+:
+ #
+ # require 'json/add/time'
+ # x = Time.now.as_json
+ # # => {"json_class"=>"Time", "s"=>1700931656, "n"=>472846644}
+ #
+ # \Method +JSON.create+ deserializes such a hash, returning a \Time object:
+ #
+ # Time.json_create(x)
+ # # => 2023-11-25 11:00:56.472846644 -0600
+ #
+ def as_json(*)
+ {
+ JSON.create_id => self.class.name,
+ 's' => tv_sec,
+ 'n' => tv_nsec,
+ }
+ end
+
+ # Returns a JSON string representing +self+:
+ #
+ # require 'json/add/time'
+ # puts Time.now.to_json
+ #
+ # Output:
+ #
+ # {"json_class":"Time","s":1700931678,"n":980650786}
+ #
+ def to_json(*args)
+ as_json.to_json(*args)
+ end
+end
diff --git a/ext/json/lib/json/common.rb b/ext/json/lib/json/common.rb
index 7bc5ae0656..230bf08012 100644
--- a/ext/json/lib/json/common.rb
+++ b/ext/json/lib/json/common.rb
@@ -1,354 +1,1173 @@
+# frozen_string_literal: true
+
require 'json/version'
module JSON
+ autoload :GenericObject, 'json/generic_object'
+
+ module ParserOptions # :nodoc:
+ class << self
+ def prepare(opts)
+ if opts[:object_class] || opts[:array_class]
+ opts = opts.dup
+ on_load = opts[:on_load]
+
+ on_load = object_class_proc(opts[:object_class], on_load) if opts[:object_class]
+ on_load = array_class_proc(opts[:array_class], on_load) if opts[:array_class]
+ opts[:on_load] = on_load
+ end
+
+ if opts.fetch(:create_additions, false) != false
+ opts = create_additions_proc(opts)
+ end
+
+ opts
+ end
+
+ private
+
+ def object_class_proc(object_class, on_load)
+ ->(obj) do
+ if Hash === obj
+ object = object_class.new
+ obj.each { |k, v| object[k] = v }
+ obj = object
+ end
+ on_load.nil? ? obj : on_load.call(obj)
+ end
+ end
+
+ def array_class_proc(array_class, on_load)
+ ->(obj) do
+ if Array === obj
+ array = array_class.new
+ obj.each { |v| array << v }
+ obj = array
+ end
+ on_load.nil? ? obj : on_load.call(obj)
+ end
+ end
+
+ # TODO: extract :create_additions support to another gem for version 3.0
+ def create_additions_proc(opts)
+ if opts[:symbolize_names]
+ raise ArgumentError, "options :symbolize_names and :create_additions cannot be used in conjunction"
+ end
+
+ opts = opts.dup
+ create_additions = opts.fetch(:create_additions, false)
+ on_load = opts[:on_load]
+ object_class = opts[:object_class] || Hash
+
+ opts[:on_load] = ->(object) do
+ case object
+ when String
+ opts[:match_string]&.each do |pattern, klass|
+ if match = pattern.match(object)
+ create_additions_warning if create_additions.nil?
+ object = klass.json_create(object)
+ break
+ end
+ end
+ when object_class
+ if opts[:create_additions] != false
+ if class_path = object[JSON.create_id]
+ klass = begin
+ Object.const_get(class_path)
+ rescue NameError => e
+ raise ArgumentError, "can't get const #{class_path}: #{e}"
+ end
+
+ if klass.respond_to?(:json_creatable?) ? klass.json_creatable? : klass.respond_to?(:json_create)
+ create_additions_warning if create_additions.nil?
+ object = klass.json_create(object)
+ end
+ end
+ end
+ end
+
+ on_load.nil? ? object : on_load.call(object)
+ end
+
+ opts
+ end
+
+ def create_additions_warning
+ JSON.deprecation_warning "JSON.load implicit support for `create_additions: true` is deprecated " \
+ "and will be removed in 3.0, use JSON.unsafe_load or explicitly " \
+ "pass `create_additions: true`"
+ end
+ end
+ end
+
class << self
- # If _object_ is string like parse the string and return the parsed result
- # as a Ruby data structure. Otherwise generate a JSON text from the Ruby
- # data structure object and return it.
- #
- # The _opts_ argument is passed through to generate/parse respectively, see
- # generate and parse for their documentation.
- def [](object, opts = {})
- if object.respond_to? :to_str
- JSON.parse(object.to_str, opts => {})
+ def deprecation_warning(message, uplevel = 3) # :nodoc:
+ gem_root = File.expand_path("..", __dir__) + "/"
+ caller_locations(uplevel, 10).each do |frame|
+ if frame.path.nil? || frame.path.start_with?(gem_root) || frame.path.end_with?("/truffle/cext_ruby.rb", ".c")
+ uplevel += 1
+ else
+ break
+ end
+ end
+
+ if RUBY_VERSION >= "3.0"
+ warn(message, uplevel: uplevel, category: :deprecated)
else
- JSON.generate(object, opts => {})
+ warn(message, uplevel: uplevel)
end
end
- # Returns the JSON parser class, that is used by JSON. This might be either
- # JSON::Ext::Parser or JSON::Pure::Parser.
+ # :call-seq:
+ # JSON[object] -> new_array or new_string
+ #
+ # If +object+ is a \String,
+ # calls JSON.parse with +object+ and +opts+ (see method #parse):
+ # json = '[0, 1, null]'
+ # JSON[json]# => [0, 1, nil]
+ #
+ # Otherwise, calls JSON.generate with +object+ and +opts+ (see method #generate):
+ # ruby = [0, 1, nil]
+ # JSON[ruby] # => '[0,1,null]'
+ def [](object, opts = nil)
+ if object.is_a?(String)
+ return JSON.parse(object, opts)
+ elsif object.respond_to?(:to_str)
+ str = object.to_str
+ if str.is_a?(String)
+ return JSON.parse(str, opts)
+ end
+ end
+
+ JSON.generate(object, opts)
+ end
+
+ # Returns the JSON parser class that is used by JSON.
attr_reader :parser
# Set the JSON parser class _parser_ to be used by JSON.
def parser=(parser) # :nodoc:
@parser = parser
- remove_const :Parser if const_defined? :Parser
+ remove_const :Parser if const_defined?(:Parser, false)
const_set :Parser, parser
end
- # Return the constant located at _path_. The format of _path_ has to be
- # either ::A::B::C or A::B::C. In any case A has to be located at the top
- # level (absolute namespace path?). If there doesn't exist a constant at
- # the given path, an ArgumentError is raised.
- def deep_const_get(path) # :nodoc:
- path = path.to_s
- path.split(/::/).inject(Object) do |p, c|
- case
- when c.empty? then p
- when p.const_defined?(c) then p.const_get(c)
- else raise ArgumentError, "can't find const #{path}"
- end
- end
- end
-
# Set the module _generator_ to be used by JSON.
def generator=(generator) # :nodoc:
+ old, $VERBOSE = $VERBOSE, nil
@generator = generator
- generator_methods = generator::GeneratorMethods
- for const in generator_methods.constants
- klass = deep_const_get(const)
- modul = generator_methods.const_get(const)
- klass.class_eval do
- instance_methods(false).each do |m|
- m.to_s == 'to_json' and remove_method m
+ if generator.const_defined?(:GeneratorMethods)
+ generator_methods = generator::GeneratorMethods
+ for const in generator_methods.constants
+ klass = const_get(const)
+ modul = generator_methods.const_get(const)
+ klass.class_eval do
+ instance_methods(false).each do |m|
+ m.to_s == 'to_json' and remove_method m
+ end
+ include modul
end
- include modul
end
end
self.state = generator::State
- const_set :State, self.state
+ const_set :State, state
+ ensure
+ $VERBOSE = old
end
- # Returns the JSON generator modul, that is used by JSON. This might be
- # either JSON::Ext::Generator or JSON::Pure::Generator.
+ # Returns the JSON generator module that is used by JSON.
attr_reader :generator
- # Returns the JSON generator state class, that is used by JSON. This might
- # be either JSON::Ext::Generator::State or JSON::Pure::Generator::State.
+ # Sets or Returns the JSON generator state class that is used by JSON.
attr_accessor :state
- # This is create identifier, that is used to decide, if the _json_create_
- # hook of a class should be called. It defaults to 'json_class'.
- attr_accessor :create_id
+ private
+
+ # Called from the extension when a hash has both string and symbol keys
+ def on_mixed_keys_hash(hash, do_raise)
+ set = {}
+ hash.each_key do |key|
+ key_str = key.to_s
+
+ if set[key_str]
+ message = "detected duplicate key #{key_str.inspect} in #{hash.inspect}"
+ if do_raise
+ raise GeneratorError, message
+ else
+ deprecation_warning("#{message}.\nThis will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`")
+ end
+ else
+ set[key_str] = true
+ end
+ end
+ end
+
+ def deprecated_singleton_attr_accessor(*attrs)
+ args = RUBY_VERSION >= "3.0" ? ", category: :deprecated" : ""
+ attrs.each do |attr|
+ singleton_class.class_eval <<~RUBY
+ def #{attr}
+ warn "JSON.#{attr} is deprecated and will be removed in json 3.0.0", uplevel: 1 #{args}
+ @#{attr}
+ end
+
+ def #{attr}=(val)
+ warn "JSON.#{attr}= is deprecated and will be removed in json 3.0.0", uplevel: 1 #{args}
+ @#{attr} = val
+ end
+
+ def _#{attr}
+ @#{attr}
+ end
+ RUBY
+ end
+ end
+ end
+
+ # Sets create identifier, which is used to decide if the _json_create_
+ # hook of a class should be called; initial value is +json_class+:
+ # JSON.create_id # => 'json_class'
+ def self.create_id=(new_value)
+ Thread.current[:"JSON.create_id"] = new_value.dup.freeze
end
- self.create_id = 'json_class'
- NaN = (-1.0) ** 0.5
+ # Returns the current create identifier.
+ # See also JSON.create_id=.
+ def self.create_id
+ Thread.current[:"JSON.create_id"] || 'json_class'
+ end
+
+ NaN = Float::NAN
- Infinity = 1.0/0
+ Infinity = Float::INFINITY
MinusInfinity = -Infinity
# The base exception for JSON errors.
class JSONError < StandardError; end
- # This exception is raised, if a parser error occurs.
- class ParserError < JSONError; end
+ # This exception is raised if a parser error occurs.
+ class ParserError < JSONError
+ attr_reader :line, :column
+ end
- # This exception is raised, if the nesting of parsed datastructures is too
+ # This exception is raised if the nesting of parsed data structures is too
# deep.
class NestingError < ParserError; end
- # This exception is raised, if a generator or unparser error occurs.
- class GeneratorError < JSONError; end
- # For backwards compatibility
- UnparserError = GeneratorError
+ # This exception is raised if a generator or unparser error occurs.
+ class GeneratorError < JSONError
+ attr_reader :invalid_object
- # If a circular data structure is encountered while unparsing
- # this exception is raised.
- class CircularDatastructure < GeneratorError; end
+ def initialize(message, invalid_object = nil)
+ super(message)
+ @invalid_object = invalid_object
+ end
- # This exception is raised, if the required unicode support is missing on the
- # system. Usually this means, that the iconv library is not installed.
- class MissingUnicodeSupport < JSONError; end
+ def detailed_message(...)
+ # Exception#detailed_message doesn't exist until Ruby 3.2
+ super_message = defined?(super) ? super : message
- module_function
+ if @invalid_object.nil?
+ super_message
+ else
+ "#{super_message}\nInvalid object: #{@invalid_object.inspect}"
+ end
+ end
+ end
+
+ # Fragment of JSON document that is to be included as is:
+ # fragment = JSON::Fragment.new("[1, 2, 3]")
+ # JSON.generate({ count: 3, items: fragments })
+ #
+ # This allows to easily assemble multiple JSON fragments that have
+ # been persisted somewhere without having to parse them nor resorting
+ # to string interpolation.
+ #
+ # Note: no validation is performed on the provided string. It is the
+ # responsibility of the caller to ensure the string contains valid JSON.
+ Fragment = Struct.new(:json) do
+ def initialize(json)
+ unless string = String.try_convert(json)
+ raise TypeError, " no implicit conversion of #{json.class} into String"
+ end
- # Parse the JSON string _source_ into a Ruby data structure and return it.
- #
- # _opts_ can have the following
- # keys:
- # * *max_nesting*: The maximum depth of nesting allowed in the parsed data
- # structures. Disable depth checking with :max_nesting => false, it defaults
- # to 19.
- # * *allow_nan*: If set to true, allow NaN, Infinity and -Infinity in
- # defiance of RFC 4627 to be parsed by the Parser. This option defaults
- # to false.
- # * *create_additions*: If set to false, the Parser doesn't create
- # additions even if a matchin class and create_id was found. This option
- # defaults to true.
- def parse(source, opts = {})
- JSON.parser.new(source, opts).parse
+ super(string)
+ end
+
+ def to_json(state = nil, *)
+ json
+ end
end
- # Parse the JSON string _source_ into a Ruby data structure and return it.
- # The bang version of the parse method, defaults to the more dangerous values
- # for the _opts_ hash, so be sure only to parse trusted _source_ strings.
- #
- # _opts_ can have the following keys:
- # * *max_nesting*: The maximum depth of nesting allowed in the parsed data
- # structures. Enable depth checking with :max_nesting => anInteger. The parse!
- # methods defaults to not doing max depth checking: This can be dangerous,
- # if someone wants to fill up your stack.
- # * *allow_nan*: If set to true, allow NaN, Infinity, and -Infinity in
- # defiance of RFC 4627 to be parsed by the Parser. This option defaults
- # to true.
- # * *create_additions*: If set to false, the Parser doesn't create
- # additions even if a matchin class and create_id was found. This option
- # defaults to true.
- def parse!(source, opts = {})
- opts = {
- :max_nesting => false,
- :allow_nan => true
- }.update(opts)
- JSON.parser.new(source, opts).parse
+ module_function
+
+ # :call-seq:
+ # JSON.parse(source, opts) -> object
+ #
+ # Returns the Ruby objects created by parsing the given +source+.
+ #
+ # Argument +source+ contains the \String to be parsed.
+ #
+ # Argument +opts+, if given, contains a \Hash of options for the parsing.
+ # See {Parsing Options}[#module-JSON-label-Parsing+Options].
+ #
+ # ---
+ #
+ # When +source+ is a \JSON array, returns a Ruby \Array:
+ # source = '["foo", 1.0, true, false, null]'
+ # ruby = JSON.parse(source)
+ # ruby # => ["foo", 1.0, true, false, nil]
+ # ruby.class # => Array
+ #
+ # When +source+ is a \JSON object, returns a Ruby \Hash:
+ # source = '{"a": "foo", "b": 1.0, "c": true, "d": false, "e": null}'
+ # ruby = JSON.parse(source)
+ # ruby # => {"a"=>"foo", "b"=>1.0, "c"=>true, "d"=>false, "e"=>nil}
+ # ruby.class # => Hash
+ #
+ # For examples of parsing for all \JSON data types, see
+ # {Parsing \JSON}[#module-JSON-label-Parsing+JSON].
+ #
+ # Parses nested JSON objects:
+ # source = <<~JSON
+ # {
+ # "name": "Dave",
+ # "age" :40,
+ # "hats": [
+ # "Cattleman's",
+ # "Panama",
+ # "Tophat"
+ # ]
+ # }
+ # JSON
+ # ruby = JSON.parse(source)
+ # ruby # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
+ #
+ # ---
+ #
+ # Raises an exception if +source+ is not valid JSON:
+ # # Raises JSON::ParserError (783: unexpected token at ''):
+ # JSON.parse('')
+ #
+ def parse(source, opts = nil)
+ opts = ParserOptions.prepare(opts) unless opts.nil?
+ Parser.parse(source, opts)
end
- # Unparse the Ruby data structure _obj_ into a single line JSON string and
- # return it. _state_ is
- # * a JSON::State object,
- # * or a Hash like object (responding to to_hash),
- # * an object convertible into a hash by a to_h method,
- # that is used as or to configure a State object.
- #
- # It defaults to a state object, that creates the shortest possible JSON text
- # in one line, checks for circular data structures and doesn't allow NaN,
- # Infinity, and -Infinity.
- #
- # A _state_ hash can have the following keys:
- # * *indent*: a string used to indent levels (default: ''),
- # * *space*: a string that is put after, a : or , delimiter (default: ''),
- # * *space_before*: a string that is put before a : pair delimiter (default: ''),
- # * *object_nl*: a string that is put at the end of a JSON object (default: ''),
- # * *array_nl*: a string that is put at the end of a JSON array (default: ''),
- # * *check_circular*: true if checking for circular data structures
- # should be done (the default), false otherwise.
- # * *allow_nan*: true if NaN, Infinity, and -Infinity should be
- # generated, otherwise an exception is thrown, if these values are
- # encountered. This options defaults to false.
- # * *max_nesting*: The maximum depth of nesting allowed in the data
- # structures from which JSON is to be generated. Disable depth checking
- # with :max_nesting => false, it defaults to 19.
- #
- # See also the fast_generate for the fastest creation method with the least
- # amount of sanity checks, and the pretty_generate method for some
- # defaults for a pretty output.
- def generate(obj, state = nil)
- if state
- state = State.from_state(state)
+ PARSE_L_OPTIONS = {
+ max_nesting: false,
+ allow_nan: true,
+ }.freeze
+ private_constant :PARSE_L_OPTIONS
+
+ # :call-seq:
+ # JSON.parse!(source, opts) -> object
+ #
+ # Calls
+ # parse(source, opts)
+ # with +source+ and possibly modified +opts+.
+ #
+ # Differences from JSON.parse:
+ # - Option +max_nesting+, if not provided, defaults to +false+,
+ # which disables checking for nesting depth.
+ # - Option +allow_nan+, if not provided, defaults to +true+.
+ def parse!(source, opts = nil)
+ if opts.nil?
+ parse(source, PARSE_L_OPTIONS)
else
- state = State.new
+ parse(source, PARSE_L_OPTIONS.merge(opts))
end
- obj.to_json(state)
end
- # :stopdoc:
- # I want to deprecate these later, so I'll first be silent about them, and later delete them.
- alias unparse generate
- module_function :unparse
- # :startdoc:
+ # :call-seq:
+ # JSON.load_file(path, opts={}) -> object
+ #
+ # Calls:
+ # parse(File.read(path), opts)
+ #
+ # See method #parse.
+ def load_file(filespec, opts = nil)
+ parse(File.read(filespec, encoding: Encoding::UTF_8), opts)
+ end
- # Unparse the Ruby data structure _obj_ into a single line JSON string and
- # return it. This method disables the checks for circles in Ruby objects, and
- # also generates NaN, Infinity, and, -Infinity float values.
+ # :call-seq:
+ # JSON.load_file!(path, opts = {})
#
- # *WARNING*: Be careful not to pass any Ruby data structures with circles as
- # _obj_ argument, because this will cause JSON to go into an infinite loop.
- def fast_generate(obj)
- obj.to_json(nil)
+ # Calls:
+ # JSON.parse!(File.read(path, opts))
+ #
+ # See method #parse!
+ def load_file!(filespec, opts = nil)
+ parse!(File.read(filespec, encoding: Encoding::UTF_8), opts)
end
- # :stopdoc:
- # I want to deprecate these later, so I'll first be silent about them, and later delete them.
- alias fast_unparse fast_generate
- module_function :fast_unparse
- # :startdoc:
+ # :call-seq:
+ # JSON.generate(obj, opts = nil) -> new_string
+ #
+ # Returns a \String containing the generated \JSON data.
+ #
+ # See also JSON.pretty_generate.
+ #
+ # Argument +obj+ is the Ruby object to be converted to \JSON.
+ #
+ # Argument +opts+, if given, contains a \Hash of options for the generation.
+ # See {Generating Options}[#module-JSON-label-Generating+Options].
+ #
+ # ---
+ #
+ # When +obj+ is an \Array, returns a \String containing a \JSON array:
+ # obj = ["foo", 1.0, true, false, nil]
+ # json = JSON.generate(obj)
+ # json # => '["foo",1.0,true,false,null]'
+ #
+ # When +obj+ is a \Hash, returns a \String containing a \JSON object:
+ # obj = {foo: 0, bar: 's', baz: :bat}
+ # json = JSON.generate(obj)
+ # json # => '{"foo":0,"bar":"s","baz":"bat"}'
+ #
+ # For examples of generating from other Ruby objects, see
+ # {Generating \JSON from Other Objects}[#module-JSON-label-Generating+JSON+from+Other+Objects].
+ #
+ # ---
+ #
+ # Raises an exception if any formatting option is not a \String.
+ #
+ # Raises an exception if +obj+ contains circular references:
+ # a = []; b = []; a.push(b); b.push(a)
+ # # Raises JSON::NestingError (nesting of 100 is too deep):
+ # JSON.generate(a)
+ #
+ def generate(obj, opts = nil)
+ if State === opts
+ opts.generate(obj)
+ else
+ State.generate(obj, opts, nil)
+ end
+ end
+
+ # :call-seq:
+ # JSON.fast_generate(obj, opts) -> new_string
+ #
+ # Arguments +obj+ and +opts+ here are the same as
+ # arguments +obj+ and +opts+ in JSON.generate.
+ #
+ # By default, generates \JSON data without checking
+ # for circular references in +obj+ (option +max_nesting+ set to +false+, disabled).
+ #
+ # Raises an exception if +obj+ contains circular references:
+ # a = []; b = []; a.push(b); b.push(a)
+ # # Raises SystemStackError (stack level too deep):
+ # JSON.fast_generate(a)
+ def fast_generate(obj, opts = nil)
+ if RUBY_VERSION >= "3.0"
+ warn "JSON.fast_generate is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1, category: :deprecated
+ else
+ warn "JSON.fast_generate is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1
+ end
+ generate(obj, opts)
+ end
+
+ PRETTY_GENERATE_OPTIONS = {
+ indent: ' ',
+ space: ' ',
+ object_nl: "\n",
+ array_nl: "\n",
+ }.freeze
+ private_constant :PRETTY_GENERATE_OPTIONS
- # Unparse the Ruby data structure _obj_ into a JSON string and return it. The
- # returned string is a prettier form of the string returned by #unparse.
+ # :call-seq:
+ # JSON.pretty_generate(obj, opts = nil) -> new_string
+ #
+ # Arguments +obj+ and +opts+ here are the same as
+ # arguments +obj+ and +opts+ in JSON.generate.
+ #
+ # Default options are:
+ # {
+ # indent: ' ', # Two spaces
+ # space: ' ', # One space
+ # array_nl: "\n", # Newline
+ # object_nl: "\n" # Newline
+ # }
+ #
+ # Example:
+ # obj = {foo: [:bar, :baz], bat: {bam: 0, bad: 1}}
+ # json = JSON.pretty_generate(obj)
+ # puts json
+ # Output:
+ # {
+ # "foo": [
+ # "bar",
+ # "baz"
+ # ],
+ # "bat": {
+ # "bam": 0,
+ # "bad": 1
+ # }
+ # }
#
- # The _opts_ argument can be used to configure the generator, see the
- # generate method for a more detailed explanation.
def pretty_generate(obj, opts = nil)
- state = JSON.state.new(
- :indent => ' ',
- :space => ' ',
- :object_nl => "\n",
- :array_nl => "\n",
- :check_circular => true
- )
+ return opts.generate(obj) if State === opts
+
+ options = PRETTY_GENERATE_OPTIONS
+
if opts
- if opts.respond_to? :to_hash
- opts = opts.to_hash
- elsif opts.respond_to? :to_h
- opts = opts.to_h
+ unless opts.is_a?(Hash)
+ if opts.respond_to? :to_hash
+ opts = opts.to_hash
+ elsif opts.respond_to? :to_h
+ opts = opts.to_h
+ else
+ raise TypeError, "can't convert #{opts.class} into Hash"
+ end
+ end
+ options = options.merge(opts)
+ end
+
+ State.generate(obj, options, nil)
+ end
+
+ # Sets or returns default options for the JSON.unsafe_load method.
+ # Initially:
+ # opts = JSON.load_default_options
+ # opts # => {:max_nesting=>false, :allow_nan=>true, :allow_blank=>true, :create_additions=>true}
+ deprecated_singleton_attr_accessor :unsafe_load_default_options
+
+ @unsafe_load_default_options = {
+ :max_nesting => false,
+ :allow_nan => true,
+ :allow_blank => true,
+ :create_additions => true,
+ }
+
+ # Sets or returns default options for the JSON.load method.
+ # Initially:
+ # opts = JSON.load_default_options
+ # opts # => {:max_nesting=>false, :allow_nan=>true, :allow_blank=>true, :create_additions=>true}
+ deprecated_singleton_attr_accessor :load_default_options
+
+ @load_default_options = {
+ :allow_nan => true,
+ :allow_blank => true,
+ :create_additions => nil,
+ }
+ # :call-seq:
+ # JSON.unsafe_load(source, options = {}) -> object
+ # JSON.unsafe_load(source, proc = nil, options = {}) -> object
+ #
+ # Returns the Ruby objects created by parsing the given +source+.
+ #
+ # BEWARE: This method is meant to serialise data from trusted user input,
+ # like from your own database server or clients under your control, it could
+ # be dangerous to allow untrusted users to pass JSON sources into it.
+ #
+ # - Argument +source+ must be, or be convertible to, a \String:
+ # - If +source+ responds to instance method +to_str+,
+ # <tt>source.to_str</tt> becomes the source.
+ # - If +source+ responds to instance method +to_io+,
+ # <tt>source.to_io.read</tt> becomes the source.
+ # - If +source+ responds to instance method +read+,
+ # <tt>source.read</tt> becomes the source.
+ # - If both of the following are true, source becomes the \String <tt>'null'</tt>:
+ # - Option +allow_blank+ specifies a truthy value.
+ # - The source, as defined above, is +nil+ or the empty \String <tt>''</tt>.
+ # - Otherwise, +source+ remains the source.
+ # - Argument +proc+, if given, must be a \Proc that accepts one argument.
+ # It will be called recursively with each result (depth-first order).
+ # See details below.
+ # - Argument +opts+, if given, contains a \Hash of options for the parsing.
+ # See {Parsing Options}[#module-JSON-label-Parsing+Options].
+ # The default options can be changed via method JSON.unsafe_load_default_options=.
+ #
+ # ---
+ #
+ # When no +proc+ is given, modifies +source+ as above and returns the result of
+ # <tt>parse(source, opts)</tt>; see #parse.
+ #
+ # Source for following examples:
+ # source = <<~JSON
+ # {
+ # "name": "Dave",
+ # "age" :40,
+ # "hats": [
+ # "Cattleman's",
+ # "Panama",
+ # "Tophat"
+ # ]
+ # }
+ # JSON
+ #
+ # Load a \String:
+ # ruby = JSON.unsafe_load(source)
+ # ruby # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
+ #
+ # Load an \IO object:
+ # require 'stringio'
+ # object = JSON.unsafe_load(StringIO.new(source))
+ # object # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
+ #
+ # Load a \File object:
+ # path = 't.json'
+ # File.write(path, source)
+ # File.open(path) do |file|
+ # JSON.unsafe_load(file)
+ # end # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
+ #
+ # ---
+ #
+ # When +proc+ is given:
+ # - Modifies +source+ as above.
+ # - Gets the +result+ from calling <tt>parse(source, opts)</tt>.
+ # - Recursively calls <tt>proc(result)</tt>.
+ # - Returns the final result.
+ #
+ # Example:
+ # require 'json'
+ #
+ # # Some classes for the example.
+ # class Base
+ # def initialize(attributes)
+ # @attributes = attributes
+ # end
+ # end
+ # class User < Base; end
+ # class Account < Base; end
+ # class Admin < Base; end
+ # # The JSON source.
+ # json = <<-EOF
+ # {
+ # "users": [
+ # {"type": "User", "username": "jane", "email": "jane@example.com"},
+ # {"type": "User", "username": "john", "email": "john@example.com"}
+ # ],
+ # "accounts": [
+ # {"account": {"type": "Account", "paid": true, "account_id": "1234"}},
+ # {"account": {"type": "Account", "paid": false, "account_id": "1235"}}
+ # ],
+ # "admins": {"type": "Admin", "password": "0wn3d"}
+ # }
+ # EOF
+ # # Deserializer method.
+ # def deserialize_obj(obj, safe_types = %w(User Account Admin))
+ # type = obj.is_a?(Hash) && obj["type"]
+ # safe_types.include?(type) ? Object.const_get(type).new(obj) : obj
+ # end
+ # # Call to JSON.unsafe_load
+ # ruby = JSON.unsafe_load(json, proc {|obj|
+ # case obj
+ # when Hash
+ # obj.each {|k, v| obj[k] = deserialize_obj v }
+ # when Array
+ # obj.map! {|v| deserialize_obj v }
+ # end
+ # obj
+ # })
+ # pp ruby
+ # Output:
+ # {"users"=>
+ # [#<User:0x00000000064c4c98
+ # @attributes=
+ # {"type"=>"User", "username"=>"jane", "email"=>"jane@example.com"}>,
+ # #<User:0x00000000064c4bd0
+ # @attributes=
+ # {"type"=>"User", "username"=>"john", "email"=>"john@example.com"}>],
+ # "accounts"=>
+ # [{"account"=>
+ # #<Account:0x00000000064c4928
+ # @attributes={"type"=>"Account", "paid"=>true, "account_id"=>"1234"}>},
+ # {"account"=>
+ # #<Account:0x00000000064c4680
+ # @attributes={"type"=>"Account", "paid"=>false, "account_id"=>"1235"}>}],
+ # "admins"=>
+ # #<Admin:0x00000000064c41f8
+ # @attributes={"type"=>"Admin", "password"=>"0wn3d"}>}
+ #
+ def unsafe_load(source, proc = nil, options = nil)
+ opts = if options.nil?
+ if proc && proc.is_a?(Hash)
+ options, proc = proc, nil
+ options
+ else
+ _unsafe_load_default_options
+ end
+ else
+ _unsafe_load_default_options.merge(options)
+ end
+
+ unless source.is_a?(String)
+ if source.respond_to? :to_str
+ source = source.to_str
+ elsif source.respond_to? :to_io
+ source = source.to_io.read
+ elsif source.respond_to?(:read)
+ source = source.read
+ end
+ end
+
+ if opts[:allow_blank] && (source.nil? || source.empty?)
+ source = 'null'
+ end
+
+ if proc
+ opts = opts.dup
+ opts[:on_load] = proc.to_proc
+ end
+
+ parse(source, opts)
+ end
+
+ # :call-seq:
+ # JSON.load(source, options = {}) -> object
+ # JSON.load(source, proc = nil, options = {}) -> object
+ #
+ # Returns the Ruby objects created by parsing the given +source+.
+ #
+ # BEWARE: This method is meant to serialise data from trusted user input,
+ # like from your own database server or clients under your control, it could
+ # be dangerous to allow untrusted users to pass JSON sources into it.
+ # If you must use it, use JSON.unsafe_load instead to make it clear.
+ #
+ # Since JSON version 2.8.0, `load` emits a deprecation warning when a
+ # non native type is deserialized, without `create_additions` being explicitly
+ # enabled, and in JSON version 3.0, `load` will have `create_additions` disabled
+ # by default.
+ #
+ # - Argument +source+ must be, or be convertible to, a \String:
+ # - If +source+ responds to instance method +to_str+,
+ # <tt>source.to_str</tt> becomes the source.
+ # - If +source+ responds to instance method +to_io+,
+ # <tt>source.to_io.read</tt> becomes the source.
+ # - If +source+ responds to instance method +read+,
+ # <tt>source.read</tt> becomes the source.
+ # - If both of the following are true, source becomes the \String <tt>'null'</tt>:
+ # - Option +allow_blank+ specifies a truthy value.
+ # - The source, as defined above, is +nil+ or the empty \String <tt>''</tt>.
+ # - Otherwise, +source+ remains the source.
+ # - Argument +proc+, if given, must be a \Proc that accepts one argument.
+ # It will be called recursively with each result (depth-first order).
+ # See details below.
+ # - Argument +opts+, if given, contains a \Hash of options for the parsing.
+ # See {Parsing Options}[#module-JSON-label-Parsing+Options].
+ # The default options can be changed via method JSON.load_default_options=.
+ #
+ # ---
+ #
+ # When no +proc+ is given, modifies +source+ as above and returns the result of
+ # <tt>parse(source, opts)</tt>; see #parse.
+ #
+ # Source for following examples:
+ # source = <<~JSON
+ # {
+ # "name": "Dave",
+ # "age" :40,
+ # "hats": [
+ # "Cattleman's",
+ # "Panama",
+ # "Tophat"
+ # ]
+ # }
+ # JSON
+ #
+ # Load a \String:
+ # ruby = JSON.load(source)
+ # ruby # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
+ #
+ # Load an \IO object:
+ # require 'stringio'
+ # object = JSON.load(StringIO.new(source))
+ # object # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
+ #
+ # Load a \File object:
+ # path = 't.json'
+ # File.write(path, source)
+ # File.open(path) do |file|
+ # JSON.load(file)
+ # end # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]}
+ #
+ # ---
+ #
+ # When +proc+ is given:
+ # - Modifies +source+ as above.
+ # - Gets the +result+ from calling <tt>parse(source, opts)</tt>.
+ # - Recursively calls <tt>proc(result)</tt>.
+ # - Returns the final result.
+ #
+ # Example:
+ # require 'json'
+ #
+ # # Some classes for the example.
+ # class Base
+ # def initialize(attributes)
+ # @attributes = attributes
+ # end
+ # end
+ # class User < Base; end
+ # class Account < Base; end
+ # class Admin < Base; end
+ # # The JSON source.
+ # json = <<-EOF
+ # {
+ # "users": [
+ # {"type": "User", "username": "jane", "email": "jane@example.com"},
+ # {"type": "User", "username": "john", "email": "john@example.com"}
+ # ],
+ # "accounts": [
+ # {"account": {"type": "Account", "paid": true, "account_id": "1234"}},
+ # {"account": {"type": "Account", "paid": false, "account_id": "1235"}}
+ # ],
+ # "admins": {"type": "Admin", "password": "0wn3d"}
+ # }
+ # EOF
+ # # Deserializer method.
+ # def deserialize_obj(obj, safe_types = %w(User Account Admin))
+ # type = obj.is_a?(Hash) && obj["type"]
+ # safe_types.include?(type) ? Object.const_get(type).new(obj) : obj
+ # end
+ # # Call to JSON.load
+ # ruby = JSON.load(json, proc {|obj|
+ # case obj
+ # when Hash
+ # obj.each {|k, v| obj[k] = deserialize_obj v }
+ # when Array
+ # obj.map! {|v| deserialize_obj v }
+ # end
+ # obj
+ # })
+ # pp ruby
+ # Output:
+ # {"users"=>
+ # [#<User:0x00000000064c4c98
+ # @attributes=
+ # {"type"=>"User", "username"=>"jane", "email"=>"jane@example.com"}>,
+ # #<User:0x00000000064c4bd0
+ # @attributes=
+ # {"type"=>"User", "username"=>"john", "email"=>"john@example.com"}>],
+ # "accounts"=>
+ # [{"account"=>
+ # #<Account:0x00000000064c4928
+ # @attributes={"type"=>"Account", "paid"=>true, "account_id"=>"1234"}>},
+ # {"account"=>
+ # #<Account:0x00000000064c4680
+ # @attributes={"type"=>"Account", "paid"=>false, "account_id"=>"1235"}>}],
+ # "admins"=>
+ # #<Admin:0x00000000064c41f8
+ # @attributes={"type"=>"Admin", "password"=>"0wn3d"}>}
+ #
+ def load(source, proc = nil, options = nil)
+ if proc && options.nil? && proc.is_a?(Hash)
+ options = proc
+ proc = nil
+ end
+
+ opts = if options.nil?
+ if proc && proc.is_a?(Hash)
+ options, proc = proc, nil
+ options
else
- raise TypeError, "can't convert #{opts.class} into Hash"
+ _load_default_options
end
- state.configure(opts)
+ else
+ _load_default_options.merge(options)
+ end
+
+ unless source.is_a?(String)
+ if source.respond_to? :to_str
+ source = source.to_str
+ elsif source.respond_to? :to_io
+ source = source.to_io.read
+ elsif source.respond_to?(:read)
+ source = source.read
+ end
+ end
+
+ if opts[:allow_blank] && (source.nil? || (String === source && source.empty?))
+ source = 'null'
+ end
+
+ if proc
+ opts = opts.dup
+ opts[:on_load] = proc.to_proc
+ end
+
+ parse(source, opts)
+ end
+
+ # Sets or returns the default options for the JSON.dump method.
+ # Initially:
+ # opts = JSON.dump_default_options
+ # opts # => {:max_nesting=>false, :allow_nan=>true}
+ deprecated_singleton_attr_accessor :dump_default_options
+ @dump_default_options = {
+ :max_nesting => false,
+ :allow_nan => true,
+ }
+
+ # :call-seq:
+ # JSON.dump(obj, io = nil, limit = nil)
+ #
+ # Dumps +obj+ as a \JSON string, i.e. calls generate on the object and returns the result.
+ #
+ # The default options can be changed via method JSON.dump_default_options.
+ #
+ # - Argument +io+, if given, should respond to method +write+;
+ # the \JSON \String is written to +io+, and +io+ is returned.
+ # If +io+ is not given, the \JSON \String is returned.
+ # - Argument +limit+, if given, is passed to JSON.generate as option +max_nesting+.
+ #
+ # ---
+ #
+ # When argument +io+ is not given, returns the \JSON \String generated from +obj+:
+ # obj = {foo: [0, 1], bar: {baz: 2, bat: 3}, bam: :bad}
+ # json = JSON.dump(obj)
+ # json # => "{\"foo\":[0,1],\"bar\":{\"baz\":2,\"bat\":3},\"bam\":\"bad\"}"
+ #
+ # When argument +io+ is given, writes the \JSON \String to +io+ and returns +io+:
+ # path = 't.json'
+ # File.open(path, 'w') do |file|
+ # JSON.dump(obj, file)
+ # end # => #<File:t.json (closed)>
+ # puts File.read(path)
+ # Output:
+ # {"foo":[0,1],"bar":{"baz":2,"bat":3},"bam":"bad"}
+ def dump(obj, anIO = nil, limit = nil, kwargs = nil)
+ if kwargs.nil?
+ if limit.nil?
+ if anIO.is_a?(Hash)
+ kwargs = anIO
+ anIO = nil
+ end
+ elsif limit.is_a?(Hash)
+ kwargs = limit
+ limit = nil
+ end
+ end
+
+ unless anIO.nil?
+ if anIO.respond_to?(:to_io)
+ anIO = anIO.to_io
+ elsif limit.nil? && !anIO.respond_to?(:write)
+ anIO, limit = nil, anIO
+ end
+ end
+
+ opts = JSON._dump_default_options
+ opts = opts.merge(:max_nesting => limit) if limit
+ opts = opts.merge(kwargs) if kwargs
+
+ begin
+ State.generate(obj, opts, anIO)
+ rescue JSON::NestingError
+ raise ArgumentError, "exceed depth limit"
end
- obj.to_json(state)
end
# :stopdoc:
- # I want to deprecate these later, so I'll first be silent about them, and later delete them.
- alias pretty_unparse pretty_generate
- module_function :pretty_unparse
- # :startdoc:
+ # All these were meant to be deprecated circa 2009, but were just set as undocumented
+ # so usage still exist in the wild.
+ def unparse(...)
+ if RUBY_VERSION >= "3.0"
+ warn "JSON.unparse is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1, category: :deprecated
+ else
+ warn "JSON.unparse is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1
+ end
+ generate(...)
+ end
+ module_function :unparse
- # Load a ruby data structure from a JSON _source_ and return it. A source can
- # either be a string like object, an IO like object, or an object responding
- # to the read method. If _proc_ was given, it will be called with any nested
- # Ruby object as an argument recursively in depth first order.
- #
- # This method is part of the implementation of the load/dump interface of
- # Marshal and YAML.
- def load(source, proc = nil)
- if source.respond_to? :to_str
- source = source.to_str
- elsif source.respond_to? :to_io
- source = source.to_io.read
+ def fast_unparse(...)
+ if RUBY_VERSION >= "3.0"
+ warn "JSON.fast_unparse is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1, category: :deprecated
else
- source = source.read
+ warn "JSON.fast_unparse is deprecated and will be removed in json 3.0.0, just use JSON.generate", uplevel: 1
end
- result = parse(source, :max_nesting => false, :allow_nan => true)
- recurse_proc(result, &proc) if proc
- result
+ generate(...)
end
+ module_function :fast_unparse
- def recurse_proc(result, &proc)
- case result
- when Array
- result.each { |x| recurse_proc x, &proc }
- proc.call result
- when Hash
- result.each { |x, y| recurse_proc x, &proc; recurse_proc y, &proc }
- proc.call result
+ def pretty_unparse(...)
+ if RUBY_VERSION >= "3.0"
+ warn "JSON.pretty_unparse is deprecated and will be removed in json 3.0.0, just use JSON.pretty_generate", uplevel: 1, category: :deprecated
else
- proc.call result
+ warn "JSON.pretty_unparse is deprecated and will be removed in json 3.0.0, just use JSON.pretty_generate", uplevel: 1
end
+ pretty_generate(...)
end
- private :recurse_proc
- module_function :recurse_proc
+ module_function :fast_unparse
- alias restore load
+ def restore(...)
+ if RUBY_VERSION >= "3.0"
+ warn "JSON.restore is deprecated and will be removed in json 3.0.0, just use JSON.load", uplevel: 1, category: :deprecated
+ else
+ warn "JSON.restore is deprecated and will be removed in json 3.0.0, just use JSON.load", uplevel: 1
+ end
+ load(...)
+ end
module_function :restore
- # Dumps _obj_ as a JSON string, i.e. calls generate on the object and returns
- # the result.
+ class << self
+ private
+
+ def const_missing(const_name)
+ case const_name
+ when :PRETTY_STATE_PROTOTYPE
+ if RUBY_VERSION >= "3.0"
+ warn "JSON::PRETTY_STATE_PROTOTYPE is deprecated and will be removed in json 3.0.0, just use JSON.pretty_generate", uplevel: 1, category: :deprecated
+ else
+ warn "JSON::PRETTY_STATE_PROTOTYPE is deprecated and will be removed in json 3.0.0, just use JSON.pretty_generate", uplevel: 1
+ end
+ state.new(PRETTY_GENERATE_OPTIONS)
+ else
+ super
+ end
+ end
+ end
+ # :startdoc:
+
+ # JSON::Coder holds a parser and generator configuration.
#
- # If anIO (an IO like object or an object that responds to the write method)
- # was given, the resulting JSON is written to it.
+ # module MyApp
+ # JSONC_CODER = JSON::Coder.new(
+ # allow_trailing_comma: true
+ # )
+ # end
#
- # If the number of nested arrays or objects exceeds _limit_ an ArgumentError
- # exception is raised. This argument is similar (but not exactly the
- # same!) to the _limit_ argument in Marshal.dump.
+ # MyApp::JSONC_CODER.load(document)
#
- # This method is part of the implementation of the load/dump interface of
- # Marshal and YAML.
- def dump(obj, anIO = nil, limit = nil)
- if anIO and limit.nil?
- anIO = anIO.to_io if anIO.respond_to?(:to_io)
- unless anIO.respond_to?(:write)
- limit = anIO
- anIO = nil
+ class Coder
+ # :call-seq:
+ # JSON.new(options = nil, &block)
+ #
+ # Argument +options+, if given, contains a \Hash of options for both parsing and generating.
+ # See {Parsing Options}[rdoc-ref:JSON@Parsing+Options],
+ # and {Generating Options}[rdoc-ref:JSON@Generating+Options].
+ #
+ # For generation, the <tt>strict: true</tt> option is always set. When a Ruby object with no native \JSON counterpart is
+ # encountered, the block provided to the initialize method is invoked, and must return a Ruby object that has a native
+ # \JSON counterpart:
+ #
+ # module MyApp
+ # API_JSON_CODER = JSON::Coder.new do |object|
+ # case object
+ # when Time
+ # object.iso8601(3)
+ # else
+ # object # Unknown type, will raise
+ # end
+ # end
+ # end
+ #
+ # puts MyApp::API_JSON_CODER.dump(Time.now.utc) # => "2025-01-21T08:41:44.286Z"
+ #
+ def initialize(options = nil, &as_json)
+ if options.nil?
+ options = { strict: true }
+ else
+ options = options.dup
+ options[:strict] = true
end
+ options[:as_json] = as_json if as_json
+
+ @state = State.new(options).freeze
+ @parser_config = Ext::Parser::Config.new(ParserOptions.prepare(options)).freeze
end
- limit ||= 0
- result = generate(obj, :allow_nan => true, :max_nesting => limit)
- if anIO
- anIO.write result
- anIO
- else
- result
+
+ # call-seq:
+ # dump(object) -> String
+ # dump(object, io) -> io
+ #
+ # Serialize the given object into a \JSON document.
+ def dump(object, io = nil)
+ @state.generate(object, io)
+ end
+ alias_method :generate, :dump
+
+ # call-seq:
+ # load(string) -> Object
+ #
+ # Parse the given \JSON document and return an equivalent Ruby object.
+ def load(source)
+ @parser_config.parse(source)
+ end
+ alias_method :parse, :load
+
+ # call-seq:
+ # load(path) -> Object
+ #
+ # Parse the given \JSON document and return an equivalent Ruby object.
+ def load_file(path)
+ load(File.read(path, encoding: Encoding::UTF_8))
+ end
+ end
+
+ module GeneratorMethods
+ # call-seq: to_json(*)
+ #
+ # Converts this object into a JSON string.
+ # If this object doesn't directly maps to a JSON native type,
+ # first convert it to a string (calling #to_s), then converts
+ # it to a JSON string, and returns the result.
+ # This is a fallback, if no special method #to_json was defined for some object.
+ def to_json(state = nil, *)
+ obj = case self
+ when nil, false, true, Integer, Float, Array, Hash
+ self
+ else
+ "#{self}"
+ end
+
+ if state.nil?
+ JSON::State._generate_no_fallback(obj, nil, nil)
+ else
+ JSON::State.from_state(state)._generate_no_fallback(obj)
+ end
end
- rescue JSON::NestingError
- raise ArgumentError, "exceed depth limit"
end
end
module ::Kernel
+ private
+
# Outputs _objs_ to STDOUT as JSON strings in the shortest form, that is in
# one line.
def j(*objs)
+ if RUBY_VERSION >= "3.0"
+ warn "Kernel#j is deprecated and will be removed in json 3.0.0", uplevel: 1, category: :deprecated
+ else
+ warn "Kernel#j is deprecated and will be removed in json 3.0.0", uplevel: 1
+ end
+
objs.each do |obj|
- puts JSON::generate(obj, :allow_nan => true, :max_nesting => false)
+ puts JSON.generate(obj, :allow_nan => true, :max_nesting => false)
end
nil
end
- # Ouputs _objs_ to STDOUT as JSON strings in a pretty format, with
+ # Outputs _objs_ to STDOUT as JSON strings in a pretty format, with
# indentation and over many lines.
def jj(*objs)
+ if RUBY_VERSION >= "3.0"
+ warn "Kernel#jj is deprecated and will be removed in json 3.0.0", uplevel: 1, category: :deprecated
+ else
+ warn "Kernel#jj is deprecated and will be removed in json 3.0.0", uplevel: 1
+ end
+
objs.each do |obj|
- puts JSON::pretty_generate(obj, :allow_nan => true, :max_nesting => false)
+ puts JSON.pretty_generate(obj, :allow_nan => true, :max_nesting => false)
end
nil
end
- # If _object_ is string like parse the string and return the parsed result as
- # a Ruby data structure. Otherwise generate a JSON text from the Ruby data
+ # If _object_ is string-like, parse the string and return the parsed result as
+ # a Ruby data structure. Otherwise, generate a JSON text from the Ruby data
# structure object and return it.
#
- # The _opts_ argument is passed through to generate/parse respectively, see
+ # The _opts_ argument is passed through to generate/parse respectively. See
# generate and parse for their documentation.
- def JSON(object, opts = {})
- if object.respond_to? :to_str
- JSON.parse(object.to_str, opts)
- else
- JSON.generate(object, opts)
- end
+ def JSON(object, opts = nil)
+ JSON[object, opts]
end
end
-class ::Class
- # Returns true, if this class can be used to create an instance
- # from a serialised JSON string. The class has to implement a class
- # method _json_create_ that expects a hash as first parameter, which includes
- # the required data.
- def json_creatable?
- respond_to?(:json_create)
- end
+class Object
+ include JSON::GeneratorMethods
end
- # vim: set et sw=2 ts=2:
diff --git a/ext/json/lib/json/editor.rb b/ext/json/lib/json/editor.rb
deleted file mode 100644
index 9a65400622..0000000000
--- a/ext/json/lib/json/editor.rb
+++ /dev/null
@@ -1,1363 +0,0 @@
-# To use the GUI JSON editor, start the edit_json.rb executable script. It
-# requires ruby-gtk to be installed.
-
-require 'gtk2'
-require 'iconv'
-require 'json'
-require 'rbconfig'
-require 'open-uri'
-
-module JSON
- module Editor
- include Gtk
-
- # Beginning of the editor window title
- TITLE = 'JSON Editor'.freeze
-
- # Columns constants
- ICON_COL, TYPE_COL, CONTENT_COL = 0, 1, 2
-
- # JSON primitive types (Containers)
- CONTAINER_TYPES = %w[Array Hash].sort
- # All JSON primitive types
- ALL_TYPES = (%w[TrueClass FalseClass Numeric String NilClass] +
- CONTAINER_TYPES).sort
-
- # The Nodes necessary for the tree representation of a JSON document
- ALL_NODES = (ALL_TYPES + %w[Key]).sort
-
- DEFAULT_DIALOG_KEY_PRESS_HANDLER = lambda do |dialog, event|
- case event.keyval
- when Gdk::Keyval::GDK_Return
- dialog.response Dialog::RESPONSE_ACCEPT
- when Gdk::Keyval::GDK_Escape
- dialog.response Dialog::RESPONSE_REJECT
- end
- end
-
- # Returns the Gdk::Pixbuf of the icon named _name_ from the icon cache.
- def Editor.fetch_icon(name)
- @icon_cache ||= {}
- unless @icon_cache.key?(name)
- path = File.dirname(__FILE__)
- @icon_cache[name] = Gdk::Pixbuf.new(File.join(path, name + '.xpm'))
- end
- @icon_cache[name]
- end
-
- # Opens an error dialog on top of _window_ showing the error message
- # _text_.
- def Editor.error_dialog(window, text)
- dialog = MessageDialog.new(window, Dialog::MODAL,
- MessageDialog::ERROR,
- MessageDialog::BUTTONS_CLOSE, text)
- dialog.show_all
- dialog.run
- rescue TypeError
- dialog = MessageDialog.new(Editor.window, Dialog::MODAL,
- MessageDialog::ERROR,
- MessageDialog::BUTTONS_CLOSE, text)
- dialog.show_all
- dialog.run
- ensure
- dialog.destroy if dialog
- end
-
- # Opens a yes/no question dialog on top of _window_ showing the error
- # message _text_. If yes was answered _true_ is returned, otherwise
- # _false_.
- def Editor.question_dialog(window, text)
- dialog = MessageDialog.new(window, Dialog::MODAL,
- MessageDialog::QUESTION,
- MessageDialog::BUTTONS_YES_NO, text)
- dialog.show_all
- dialog.run do |response|
- return Gtk::Dialog::RESPONSE_YES === response
- end
- ensure
- dialog.destroy if dialog
- end
-
- # Convert the tree model starting from Gtk::TreeIter _iter_ into a Ruby
- # data structure and return it.
- def Editor.model2data(iter)
- return nil if iter.nil?
- case iter.type
- when 'Hash'
- hash = {}
- iter.each { |c| hash[c.content] = Editor.model2data(c.first_child) }
- hash
- when 'Array'
- array = Array.new(iter.n_children)
- iter.each_with_index { |c, i| array[i] = Editor.model2data(c) }
- array
- when 'Key'
- iter.content
- when 'String'
- iter.content
- when 'Numeric'
- content = iter.content
- if /\./.match(content)
- content.to_f
- else
- content.to_i
- end
- when 'TrueClass'
- true
- when 'FalseClass'
- false
- when 'NilClass'
- nil
- else
- fail "Unknown type found in model: #{iter.type}"
- end
- end
-
- # Convert the Ruby data structure _data_ into tree model data for Gtk and
- # returns the whole model. If the parameter _model_ wasn't given a new
- # Gtk::TreeStore is created as the model. The _parent_ parameter specifies
- # the parent node (iter, Gtk:TreeIter instance) to which the data is
- # appended, alternativeley the result of the yielded block is used as iter.
- def Editor.data2model(data, model = nil, parent = nil)
- model ||= TreeStore.new(Gdk::Pixbuf, String, String)
- iter = if block_given?
- yield model
- else
- model.append(parent)
- end
- case data
- when Hash
- iter.type = 'Hash'
- data.sort.each do |key, value|
- pair_iter = model.append(iter)
- pair_iter.type = 'Key'
- pair_iter.content = key.to_s
- Editor.data2model(value, model, pair_iter)
- end
- when Array
- iter.type = 'Array'
- data.each do |value|
- Editor.data2model(value, model, iter)
- end
- when Numeric
- iter.type = 'Numeric'
- iter.content = data.to_s
- when String, true, false, nil
- iter.type = data.class.name
- iter.content = data.nil? ? 'null' : data.to_s
- else
- iter.type = 'String'
- iter.content = data.to_s
- end
- model
- end
-
- # The Gtk::TreeIter class is reopened and some auxiliary methods are added.
- class Gtk::TreeIter
- include Enumerable
-
- # Traverse each of this Gtk::TreeIter instance's children
- # and yield to them.
- def each
- n_children.times { |i| yield nth_child(i) }
- end
-
- # Recursively traverse all nodes of this Gtk::TreeIter's subtree
- # (including self) and yield to them.
- def recursive_each(&block)
- yield self
- each do |i|
- i.recursive_each(&block)
- end
- end
-
- # Remove the subtree of this Gtk::TreeIter instance from the
- # model _model_.
- def remove_subtree(model)
- while current = first_child
- model.remove(current)
- end
- end
-
- # Returns the type of this node.
- def type
- self[TYPE_COL]
- end
-
- # Sets the type of this node to _value_. This implies setting
- # the respective icon accordingly.
- def type=(value)
- self[TYPE_COL] = value
- self[ICON_COL] = Editor.fetch_icon(value)
- end
-
- # Returns the content of this node.
- def content
- self[CONTENT_COL]
- end
-
- # Sets the content of this node to _value_.
- def content=(value)
- self[CONTENT_COL] = value
- end
- end
-
- # This module bundles some method, that can be used to create a menu. It
- # should be included into the class in question.
- module MenuExtension
- include Gtk
-
- # Creates a Menu, that includes MenuExtension. _treeview_ is the
- # Gtk::TreeView, on which it operates.
- def initialize(treeview)
- @treeview = treeview
- @menu = Menu.new
- end
-
- # Returns the Gtk::TreeView of this menu.
- attr_reader :treeview
-
- # Returns the menu.
- attr_reader :menu
-
- # Adds a Gtk::SeparatorMenuItem to this instance's #menu.
- def add_separator
- menu.append SeparatorMenuItem.new
- end
-
- # Adds a Gtk::MenuItem to this instance's #menu. _label_ is the label
- # string, _klass_ is the item type, and _callback_ is the procedure, that
- # is called if the _item_ is activated.
- def add_item(label, keyval = nil, klass = MenuItem, &callback)
- label = "#{label} (C-#{keyval.chr})" if keyval
- item = klass.new(label)
- item.signal_connect(:activate, &callback)
- if keyval
- self.signal_connect(:'key-press-event') do |item, event|
- if event.state & Gdk::Window::ModifierType::CONTROL_MASK != 0 and
- event.keyval == keyval
- callback.call item
- end
- end
- end
- menu.append item
- item
- end
-
- # This method should be implemented in subclasses to create the #menu of
- # this instance. It has to be called after an instance of this class is
- # created, to build the menu.
- def create
- raise NotImplementedError
- end
-
- def method_missing(*a, &b)
- treeview.__send__(*a, &b)
- end
- end
-
- # This class creates the popup menu, that opens when clicking onto the
- # treeview.
- class PopUpMenu
- include MenuExtension
-
- # Change the type or content of the selected node.
- def change_node(item)
- if current = selection.selected
- parent = current.parent
- old_type, old_content = current.type, current.content
- if ALL_TYPES.include?(old_type)
- @clipboard_data = Editor.model2data(current)
- type, content = ask_for_element(parent, current.type,
- current.content)
- if type
- current.type, current.content = type, content
- current.remove_subtree(model)
- toplevel.display_status("Changed a node in tree.")
- window.change
- end
- else
- toplevel.display_status(
- "Cannot change node of type #{old_type} in tree!")
- end
- end
- end
-
- # Cut the selected node and its subtree, and save it into the
- # clipboard.
- def cut_node(item)
- if current = selection.selected
- if current and current.type == 'Key'
- @clipboard_data = {
- current.content => Editor.model2data(current.first_child)
- }
- else
- @clipboard_data = Editor.model2data(current)
- end
- model.remove(current)
- window.change
- toplevel.display_status("Cut a node from tree.")
- end
- end
-
- # Copy the selected node and its subtree, and save it into the
- # clipboard.
- def copy_node(item)
- if current = selection.selected
- if current and current.type == 'Key'
- @clipboard_data = {
- current.content => Editor.model2data(current.first_child)
- }
- else
- @clipboard_data = Editor.model2data(current)
- end
- window.change
- toplevel.display_status("Copied a node from tree.")
- end
- end
-
- # Paste the data in the clipboard into the selected Array or Hash by
- # appending it.
- def paste_node_appending(item)
- if current = selection.selected
- if @clipboard_data
- case current.type
- when 'Array'
- Editor.data2model(@clipboard_data, model, current)
- expand_collapse(current)
- when 'Hash'
- if @clipboard_data.is_a? Hash
- parent = current.parent
- hash = Editor.model2data(current)
- model.remove(current)
- hash.update(@clipboard_data)
- Editor.data2model(hash, model, parent)
- if parent
- expand_collapse(parent)
- elsif @expanded
- expand_all
- end
- window.change
- else
- toplevel.display_status(
- "Cannot paste non-#{current.type} data into '#{current.type}'!")
- end
- else
- toplevel.display_status(
- "Cannot paste node below '#{current.type}'!")
- end
- else
- toplevel.display_status("Nothing to paste in clipboard!")
- end
- else
- toplevel.display_status("Append a node into the root first!")
- end
- end
-
- # Paste the data in the clipboard into the selected Array inserting it
- # before the selected element.
- def paste_node_inserting_before(item)
- if current = selection.selected
- if @clipboard_data
- parent = current.parent or return
- parent_type = parent.type
- if parent_type == 'Array'
- selected_index = parent.each_with_index do |c, i|
- break i if c == current
- end
- Editor.data2model(@clipboard_data, model, parent) do |m|
- m.insert_before(parent, current)
- end
- expand_collapse(current)
- toplevel.display_status("Inserted an element to " +
- "'#{parent_type}' before index #{selected_index}.")
- window.change
- else
- toplevel.display_status(
- "Cannot insert node below '#{parent_type}'!")
- end
- else
- toplevel.display_status("Nothing to paste in clipboard!")
- end
- else
- toplevel.display_status("Append a node into the root first!")
- end
- end
-
- # Append a new node to the selected Hash or Array.
- def append_new_node(item)
- if parent = selection.selected
- parent_type = parent.type
- case parent_type
- when 'Hash'
- key, type, content = ask_for_hash_pair(parent)
- key or return
- iter = create_node(parent, 'Key', key)
- iter = create_node(iter, type, content)
- toplevel.display_status(
- "Added a (key, value)-pair to '#{parent_type}'.")
- window.change
- when 'Array'
- type, content = ask_for_element(parent)
- type or return
- iter = create_node(parent, type, content)
- window.change
- toplevel.display_status("Appendend an element to '#{parent_type}'.")
- else
- toplevel.display_status("Cannot append to '#{parent_type}'!")
- end
- else
- type, content = ask_for_element
- type or return
- iter = create_node(nil, type, content)
- window.change
- end
- end
-
- # Insert a new node into an Array before the selected element.
- def insert_new_node(item)
- if current = selection.selected
- parent = current.parent or return
- parent_parent = parent.parent
- parent_type = parent.type
- if parent_type == 'Array'
- selected_index = parent.each_with_index do |c, i|
- break i if c == current
- end
- type, content = ask_for_element(parent)
- type or return
- iter = model.insert_before(parent, current)
- iter.type, iter.content = type, content
- toplevel.display_status("Inserted an element to " +
- "'#{parent_type}' before index #{selected_index}.")
- window.change
- else
- toplevel.display_status(
- "Cannot insert node below '#{parent_type}'!")
- end
- else
- toplevel.display_status("Append a node into the root first!")
- end
- end
-
- # Recursively collapse/expand a subtree starting from the selected node.
- def collapse_expand(item)
- if current = selection.selected
- if row_expanded?(current.path)
- collapse_row(current.path)
- else
- expand_row(current.path, true)
- end
- else
- toplevel.display_status("Append a node into the root first!")
- end
- end
-
- # Create the menu.
- def create
- add_item("Change node", ?n, &method(:change_node))
- add_separator
- add_item("Cut node", ?X, &method(:cut_node))
- add_item("Copy node", ?C, &method(:copy_node))
- add_item("Paste node (appending)", ?A, &method(:paste_node_appending))
- add_item("Paste node (inserting before)", ?I,
- &method(:paste_node_inserting_before))
- add_separator
- add_item("Append new node", ?a, &method(:append_new_node))
- add_item("Insert new node before", ?i, &method(:insert_new_node))
- add_separator
- add_item("Collapse/Expand node (recursively)", ?e,
- &method(:collapse_expand))
-
- menu.show_all
- signal_connect(:button_press_event) do |widget, event|
- if event.kind_of? Gdk::EventButton and event.button == 3
- menu.popup(nil, nil, event.button, event.time)
- end
- end
- signal_connect(:popup_menu) do
- menu.popup(nil, nil, 0, Gdk::Event::CURRENT_TIME)
- end
- end
- end
-
- # This class creates the File pulldown menu.
- class FileMenu
- include MenuExtension
-
- # Clear the model and filename, but ask to save the JSON document, if
- # unsaved changes have occured.
- def new(item)
- window.clear
- end
-
- # Open a file and load it into the editor. Ask to save the JSON document
- # first, if unsaved changes have occured.
- def open(item)
- window.file_open
- end
-
- def open_location(item)
- window.location_open
- end
-
- # Revert the current JSON document in the editor to the saved version.
- def revert(item)
- window.instance_eval do
- @filename and file_open(@filename)
- end
- end
-
- # Save the current JSON document.
- def save(item)
- window.file_save
- end
-
- # Save the current JSON document under the given filename.
- def save_as(item)
- window.file_save_as
- end
-
- # Quit the editor, after asking to save any unsaved changes first.
- def quit(item)
- window.quit
- end
-
- # Create the menu.
- def create
- title = MenuItem.new('File')
- title.submenu = menu
- add_item('New', &method(:new))
- add_item('Open', ?o, &method(:open))
- add_item('Open location', ?l, &method(:open_location))
- add_item('Revert', &method(:revert))
- add_separator
- add_item('Save', ?s, &method(:save))
- add_item('Save As', ?S, &method(:save_as))
- add_separator
- add_item('Quit', ?q, &method(:quit))
- title
- end
- end
-
- # This class creates the Edit pulldown menu.
- class EditMenu
- include MenuExtension
-
- # Copy data from model into primary clipboard.
- def copy(item)
- data = Editor.model2data(model.iter_first)
- json = JSON.pretty_generate(data, :max_nesting => false)
- c = Gtk::Clipboard.get(Gdk::Selection::PRIMARY)
- c.text = json
- end
-
- # Copy json text from primary clipboard into model.
- def paste(item)
- c = Gtk::Clipboard.get(Gdk::Selection::PRIMARY)
- if json = c.wait_for_text
- window.ask_save if @changed
- begin
- window.edit json
- rescue JSON::ParserError
- window.clear
- end
- end
- end
-
- # Find a string in all nodes' contents and select the found node in the
- # treeview.
- def find(item)
- @search = ask_for_find_term(@search) or return
- iter = model.get_iter('0') or return
- iter.recursive_each do |i|
- if @iter
- if @iter != i
- next
- else
- @iter = nil
- next
- end
- elsif @search.match(i[CONTENT_COL])
- set_cursor(i.path, nil, false)
- @iter = i
- break
- end
- end
- end
-
- # Repeat the last search given by #find.
- def find_again(item)
- @search or return
- iter = model.get_iter('0')
- iter.recursive_each do |i|
- if @iter
- if @iter != i
- next
- else
- @iter = nil
- next
- end
- elsif @search.match(i[CONTENT_COL])
- set_cursor(i.path, nil, false)
- @iter = i
- break
- end
- end
- end
-
- # Sort (Reverse sort) all elements of the selected array by the given
- # expression. _x_ is the element in question.
- def sort(item)
- if current = selection.selected
- if current.type == 'Array'
- parent = current.parent
- ary = Editor.model2data(current)
- order, reverse = ask_for_order
- order or return
- begin
- block = eval "lambda { |x| #{order} }"
- if reverse
- ary.sort! { |a,b| block[b] <=> block[a] }
- else
- ary.sort! { |a,b| block[a] <=> block[b] }
- end
- rescue => e
- Editor.error_dialog(self, "Failed to sort Array with #{order}: #{e}!")
- else
- Editor.data2model(ary, model, parent) do |m|
- m.insert_before(parent, current)
- end
- model.remove(current)
- expand_collapse(parent)
- window.change
- toplevel.display_status("Array has been sorted.")
- end
- else
- toplevel.display_status("Only Array nodes can be sorted!")
- end
- else
- toplevel.display_status("Select an Array to sort first!")
- end
- end
-
- # Create the menu.
- def create
- title = MenuItem.new('Edit')
- title.submenu = menu
- add_item('Copy', ?c, &method(:copy))
- add_item('Paste', ?v, &method(:paste))
- add_separator
- add_item('Find', ?f, &method(:find))
- add_item('Find Again', ?g, &method(:find_again))
- add_separator
- add_item('Sort', ?S, &method(:sort))
- title
- end
- end
-
- class OptionsMenu
- include MenuExtension
-
- # Collapse/Expand all nodes by default.
- def collapsed_nodes(item)
- if expanded
- self.expanded = false
- collapse_all
- else
- self.expanded = true
- expand_all
- end
- end
-
- # Toggle pretty saving mode on/off.
- def pretty_saving(item)
- @pretty_item.toggled
- window.change
- end
-
- attr_reader :pretty_item
-
- # Create the menu.
- def create
- title = MenuItem.new('Options')
- title.submenu = menu
- add_item('Collapsed nodes', nil, CheckMenuItem, &method(:collapsed_nodes))
- @pretty_item = add_item('Pretty saving', nil, CheckMenuItem,
- &method(:pretty_saving))
- @pretty_item.active = true
- window.unchange
- title
- end
- end
-
- # This class inherits from Gtk::TreeView, to configure it and to add a lot
- # of behaviour to it.
- class JSONTreeView < Gtk::TreeView
- include Gtk
-
- # Creates a JSONTreeView instance, the parameter _window_ is
- # a MainWindow instance and used for self delegation.
- def initialize(window)
- @window = window
- super(TreeStore.new(Gdk::Pixbuf, String, String))
- self.selection.mode = SELECTION_BROWSE
-
- @expanded = false
- self.headers_visible = false
- add_columns
- add_popup_menu
- end
-
- # Returns the MainWindow instance of this JSONTreeView.
- attr_reader :window
-
- # Returns true, if nodes are autoexpanding, false otherwise.
- attr_accessor :expanded
-
- private
-
- def add_columns
- cell = CellRendererPixbuf.new
- column = TreeViewColumn.new('Icon', cell,
- 'pixbuf' => ICON_COL
- )
- append_column(column)
-
- cell = CellRendererText.new
- column = TreeViewColumn.new('Type', cell,
- 'text' => TYPE_COL
- )
- append_column(column)
-
- cell = CellRendererText.new
- cell.editable = true
- column = TreeViewColumn.new('Content', cell,
- 'text' => CONTENT_COL
- )
- cell.signal_connect(:edited, &method(:cell_edited))
- append_column(column)
- end
-
- def unify_key(iter, key)
- return unless iter.type == 'Key'
- parent = iter.parent
- if parent.any? { |c| c != iter and c.content == key }
- old_key = key
- i = 0
- begin
- key = sprintf("%s.%d", old_key, i += 1)
- end while parent.any? { |c| c != iter and c.content == key }
- end
- iter.content = key
- end
-
- def cell_edited(cell, path, value)
- iter = model.get_iter(path)
- case iter.type
- when 'Key'
- unify_key(iter, value)
- toplevel.display_status('Key has been changed.')
- when 'FalseClass'
- value.downcase!
- if value == 'true'
- iter.type, iter.content = 'TrueClass', 'true'
- end
- when 'TrueClass'
- value.downcase!
- if value == 'false'
- iter.type, iter.content = 'FalseClass', 'false'
- end
- when 'Numeric'
- iter.content = (Integer(value) rescue Float(value) rescue 0).to_s
- when 'String'
- iter.content = value
- when 'Hash', 'Array'
- return
- else
- fail "Unknown type found in model: #{iter.type}"
- end
- window.change
- end
-
- def configure_value(value, type)
- value.editable = false
- case type
- when 'Array', 'Hash'
- value.text = ''
- when 'TrueClass'
- value.text = 'true'
- when 'FalseClass'
- value.text = 'false'
- when 'NilClass'
- value.text = 'null'
- when 'Numeric', 'String'
- value.text ||= ''
- value.editable = true
- else
- raise ArgumentError, "unknown type '#{type}' encountered"
- end
- end
-
- def add_popup_menu
- menu = PopUpMenu.new(self)
- menu.create
- end
-
- public
-
- # Create a _type_ node with content _content_, and add it to _parent_
- # in the model. If _parent_ is nil, create a new model and put it into
- # the editor treeview.
- def create_node(parent, type, content)
- iter = if parent
- model.append(parent)
- else
- new_model = Editor.data2model(nil)
- toplevel.view_new_model(new_model)
- new_model.iter_first
- end
- iter.type, iter.content = type, content
- expand_collapse(parent) if parent
- iter
- end
-
- # Ask for a hash key, value pair to be added to the Hash node _parent_.
- def ask_for_hash_pair(parent)
- key_input = type_input = value_input = nil
-
- dialog = Dialog.new("New (key, value) pair for Hash", nil, nil,
- [ Stock::OK, Dialog::RESPONSE_ACCEPT ],
- [ Stock::CANCEL, Dialog::RESPONSE_REJECT ]
- )
- dialog.width_request = 640
-
- hbox = HBox.new(false, 5)
- hbox.pack_start(Label.new("Key:"), false)
- hbox.pack_start(key_input = Entry.new)
- key_input.text = @key || ''
- dialog.vbox.pack_start(hbox, false)
- key_input.signal_connect(:activate) do
- if parent.any? { |c| c.content == key_input.text }
- toplevel.display_status('Key already exists in Hash!')
- key_input.text = ''
- else
- toplevel.display_status('Key has been changed.')
- end
- end
-
- hbox = HBox.new(false, 5)
- hbox.pack_start(Label.new("Type:"), false)
- hbox.pack_start(type_input = ComboBox.new(true))
- ALL_TYPES.each { |t| type_input.append_text(t) }
- type_input.active = @type || 0
- dialog.vbox.pack_start(hbox, false)
-
- type_input.signal_connect(:changed) do
- value_input.editable = false
- case ALL_TYPES[type_input.active]
- when 'Array', 'Hash'
- value_input.text = ''
- when 'TrueClass'
- value_input.text = 'true'
- when 'FalseClass'
- value_input.text = 'false'
- when 'NilClass'
- value_input.text = 'null'
- else
- value_input.text = ''
- value_input.editable = true
- end
- end
-
- hbox = HBox.new(false, 5)
- hbox.pack_start(Label.new("Value:"), false)
- hbox.pack_start(value_input = Entry.new)
- value_input.width_chars = 60
- value_input.text = @value || ''
- dialog.vbox.pack_start(hbox, false)
-
- dialog.signal_connect(:'key-press-event', &DEFAULT_DIALOG_KEY_PRESS_HANDLER)
- dialog.show_all
- self.focus = dialog
- dialog.run do |response|
- if response == Dialog::RESPONSE_ACCEPT
- @key = key_input.text
- type = ALL_TYPES[@type = type_input.active]
- content = value_input.text
- return @key, type, content
- end
- end
- return
- ensure
- dialog.destroy
- end
-
- # Ask for an element to be appended _parent_.
- def ask_for_element(parent = nil, default_type = nil, value_text = @content)
- type_input = value_input = nil
-
- dialog = Dialog.new(
- "New element into #{parent ? parent.type : 'root'}",
- nil, nil,
- [ Stock::OK, Dialog::RESPONSE_ACCEPT ],
- [ Stock::CANCEL, Dialog::RESPONSE_REJECT ]
- )
- hbox = HBox.new(false, 5)
- hbox.pack_start(Label.new("Type:"), false)
- hbox.pack_start(type_input = ComboBox.new(true))
- default_active = 0
- types = parent ? ALL_TYPES : CONTAINER_TYPES
- types.each_with_index do |t, i|
- type_input.append_text(t)
- if t == default_type
- default_active = i
- end
- end
- type_input.active = default_active
- dialog.vbox.pack_start(hbox, false)
- type_input.signal_connect(:changed) do
- configure_value(value_input, types[type_input.active])
- end
-
- hbox = HBox.new(false, 5)
- hbox.pack_start(Label.new("Value:"), false)
- hbox.pack_start(value_input = Entry.new)
- value_input.width_chars = 60
- value_input.text = value_text if value_text
- configure_value(value_input, types[type_input.active])
-
- dialog.vbox.pack_start(hbox, false)
-
- dialog.signal_connect(:'key-press-event', &DEFAULT_DIALOG_KEY_PRESS_HANDLER)
- dialog.show_all
- self.focus = dialog
- dialog.run do |response|
- if response == Dialog::RESPONSE_ACCEPT
- type = types[type_input.active]
- @content = case type
- when 'Numeric'
- Integer(value_input.text) rescue Float(value_input.text) rescue 0
- else
- value_input.text
- end.to_s
- return type, @content
- end
- end
- return
- ensure
- dialog.destroy if dialog
- end
-
- # Ask for an order criteria for sorting, using _x_ for the element in
- # question. Returns the order criterium, and true/false for reverse
- # sorting.
- def ask_for_order
- dialog = Dialog.new(
- "Give an order criterium for 'x'.",
- nil, nil,
- [ Stock::OK, Dialog::RESPONSE_ACCEPT ],
- [ Stock::CANCEL, Dialog::RESPONSE_REJECT ]
- )
- hbox = HBox.new(false, 5)
-
- hbox.pack_start(Label.new("Order:"), false)
- hbox.pack_start(order_input = Entry.new)
- order_input.text = @order || 'x'
- order_input.width_chars = 60
-
- hbox.pack_start(reverse_checkbox = CheckButton.new('Reverse'), false)
-
- dialog.vbox.pack_start(hbox, false)
-
- dialog.signal_connect(:'key-press-event', &DEFAULT_DIALOG_KEY_PRESS_HANDLER)
- dialog.show_all
- self.focus = dialog
- dialog.run do |response|
- if response == Dialog::RESPONSE_ACCEPT
- return @order = order_input.text, reverse_checkbox.active?
- end
- end
- return
- ensure
- dialog.destroy if dialog
- end
-
- # Ask for a find term to search for in the tree. Returns the term as a
- # string.
- def ask_for_find_term(search = nil)
- dialog = Dialog.new(
- "Find a node matching regex in tree.",
- nil, nil,
- [ Stock::OK, Dialog::RESPONSE_ACCEPT ],
- [ Stock::CANCEL, Dialog::RESPONSE_REJECT ]
- )
- hbox = HBox.new(false, 5)
-
- hbox.pack_start(Label.new("Regex:"), false)
- hbox.pack_start(regex_input = Entry.new)
- hbox.pack_start(icase_checkbox = CheckButton.new('Icase'), false)
- regex_input.width_chars = 60
- if search
- regex_input.text = search.source
- icase_checkbox.active = search.casefold?
- end
-
- dialog.vbox.pack_start(hbox, false)
-
- dialog.signal_connect(:'key-press-event', &DEFAULT_DIALOG_KEY_PRESS_HANDLER)
- dialog.show_all
- self.focus = dialog
- dialog.run do |response|
- if response == Dialog::RESPONSE_ACCEPT
- begin
- return Regexp.new(regex_input.text, icase_checkbox.active? ? Regexp::IGNORECASE : 0)
- rescue => e
- Editor.error_dialog(self, "Evaluation of regex /#{regex_input.text}/ failed: #{e}!")
- return
- end
- end
- end
- return
- ensure
- dialog.destroy if dialog
- end
-
- # Expand or collapse row pointed to by _iter_ according
- # to the #expanded attribute.
- def expand_collapse(iter)
- if expanded
- expand_row(iter.path, true)
- else
- collapse_row(iter.path)
- end
- end
- end
-
- # The editor main window
- class MainWindow < Gtk::Window
- include Gtk
-
- def initialize(encoding)
- @changed = false
- @encoding = encoding
- super(TOPLEVEL)
- display_title
- set_default_size(800, 600)
- signal_connect(:delete_event) { quit }
-
- vbox = VBox.new(false, 0)
- add(vbox)
- #vbox.border_width = 0
-
- @treeview = JSONTreeView.new(self)
- @treeview.signal_connect(:'cursor-changed') do
- display_status('')
- end
-
- menu_bar = create_menu_bar
- vbox.pack_start(menu_bar, false, false, 0)
-
- sw = ScrolledWindow.new(nil, nil)
- sw.shadow_type = SHADOW_ETCHED_IN
- sw.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
- vbox.pack_start(sw, true, true, 0)
- sw.add(@treeview)
-
- @status_bar = Statusbar.new
- vbox.pack_start(@status_bar, false, false, 0)
-
- @filename ||= nil
- if @filename
- data = read_data(@filename)
- view_new_model Editor.data2model(data)
- end
-
- signal_connect(:button_release_event) do |_,event|
- if event.button == 2
- c = Gtk::Clipboard.get(Gdk::Selection::PRIMARY)
- if url = c.wait_for_text
- location_open url
- end
- false
- else
- true
- end
- end
- end
-
- # Creates the menu bar with the pulldown menus and returns it.
- def create_menu_bar
- menu_bar = MenuBar.new
- @file_menu = FileMenu.new(@treeview)
- menu_bar.append @file_menu.create
- @edit_menu = EditMenu.new(@treeview)
- menu_bar.append @edit_menu.create
- @options_menu = OptionsMenu.new(@treeview)
- menu_bar.append @options_menu.create
- menu_bar
- end
-
- # Sets editor status to changed, to indicate that the edited data
- # containts unsaved changes.
- def change
- @changed = true
- display_title
- end
-
- # Sets editor status to unchanged, to indicate that the edited data
- # doesn't containt unsaved changes.
- def unchange
- @changed = false
- display_title
- end
-
- # Puts a new model _model_ into the Gtk::TreeView to be edited.
- def view_new_model(model)
- @treeview.model = model
- @treeview.expanded = true
- @treeview.expand_all
- unchange
- end
-
- # Displays _text_ in the status bar.
- def display_status(text)
- @cid ||= nil
- @status_bar.pop(@cid) if @cid
- @cid = @status_bar.get_context_id('dummy')
- @status_bar.push(@cid, text)
- end
-
- # Opens a dialog, asking, if changes should be saved to a file.
- def ask_save
- if Editor.question_dialog(self,
- "Unsaved changes to JSON model. Save?")
- if @filename
- file_save
- else
- file_save_as
- end
- end
- end
-
- # Quit this editor, that is, leave this editor's main loop.
- def quit
- ask_save if @changed
- if Gtk.main_level > 0
- destroy
- Gtk.main_quit
- end
- nil
- end
-
- # Display the new title according to the editor's current state.
- def display_title
- title = TITLE.dup
- title << ": #@filename" if @filename
- title << " *" if @changed
- self.title = title
- end
-
- # Clear the current model, after asking to save all unsaved changes.
- def clear
- ask_save if @changed
- @filename = nil
- self.view_new_model nil
- end
-
- def check_pretty_printed(json)
- pretty = !!((nl_index = json.index("\n")) && nl_index != json.size - 1)
- @options_menu.pretty_item.active = pretty
- end
- private :check_pretty_printed
-
- # Open the data at the location _uri_, if given. Otherwise open a dialog
- # to ask for the _uri_.
- def location_open(uri = nil)
- uri = ask_for_location unless uri
- uri or return
- ask_save if @changed
- data = load_location(uri) or return
- view_new_model Editor.data2model(data)
- end
-
- # Open the file _filename_ or call the #select_file method to ask for a
- # filename.
- def file_open(filename = nil)
- filename = select_file('Open as a JSON file') unless filename
- data = load_file(filename) or return
- view_new_model Editor.data2model(data)
- end
-
- # Edit the string _json_ in the editor.
- def edit(json)
- if json.respond_to? :read
- json = json.read
- end
- data = parse_json json
- view_new_model Editor.data2model(data)
- end
-
- # Save the current file.
- def file_save
- if @filename
- store_file(@filename)
- else
- file_save_as
- end
- end
-
- # Save the current file as the filename
- def file_save_as
- filename = select_file('Save as a JSON file')
- store_file(filename)
- end
-
- # Store the current JSON document to _path_.
- def store_file(path)
- if path
- data = Editor.model2data(@treeview.model.iter_first)
- File.open(path + '.tmp', 'wb') do |output|
- data or break
- if @options_menu.pretty_item.active?
- output.puts JSON.pretty_generate(data, :max_nesting => false)
- else
- output.write JSON.generate(data, :max_nesting => false)
- end
- end
- File.rename path + '.tmp', path
- @filename = path
- toplevel.display_status("Saved data to '#@filename'.")
- unchange
- end
- rescue SystemCallError => e
- Editor.error_dialog(self, "Failed to store JSON file: #{e}!")
- end
-
- # Load the file named _filename_ into the editor as a JSON document.
- def load_file(filename)
- if filename
- if File.directory?(filename)
- Editor.error_dialog(self, "Try to select a JSON file!")
- nil
- else
- @filename = filename
- if data = read_data(filename)
- toplevel.display_status("Loaded data from '#@filename'.")
- end
- display_title
- data
- end
- end
- end
-
- # Load the data at location _uri_ into the editor as a JSON document.
- def load_location(uri)
- data = read_data(uri) or return
- @filename = nil
- toplevel.display_status("Loaded data from '#{uri}'.")
- display_title
- data
- end
-
- def parse_json(json)
- check_pretty_printed(json)
- if @encoding && !/^utf8$/i.match(@encoding)
- iconverter = Iconv.new('utf8', @encoding)
- json = iconverter.iconv(json)
- end
- JSON::parse(json, :max_nesting => false, :create_additions => false)
- end
- private :parse_json
-
- # Read a JSON document from the file named _filename_, parse it into a
- # ruby data structure, and return the data.
- def read_data(filename)
- open(filename) do |f|
- json = f.read
- return parse_json(json)
- end
- rescue => e
- Editor.error_dialog(self, "Failed to parse JSON file: #{e}!")
- return
- end
-
- # Open a file selecton dialog, displaying _message_, and return the
- # selected filename or nil, if no file was selected.
- def select_file(message)
- filename = nil
- fs = FileSelection.new(message)
- fs.set_modal(true)
- @default_dir = File.join(Dir.pwd, '') unless @default_dir
- fs.set_filename(@default_dir)
- fs.set_transient_for(self)
- fs.signal_connect(:destroy) { Gtk.main_quit }
- fs.ok_button.signal_connect(:clicked) do
- filename = fs.filename
- @default_dir = File.join(File.dirname(filename), '')
- fs.destroy
- Gtk.main_quit
- end
- fs.cancel_button.signal_connect(:clicked) do
- fs.destroy
- Gtk.main_quit
- end
- fs.show_all
- Gtk.main
- filename
- end
-
- # Ask for location URI a to load data from. Returns the URI as a string.
- def ask_for_location
- dialog = Dialog.new(
- "Load data from location...",
- nil, nil,
- [ Stock::OK, Dialog::RESPONSE_ACCEPT ],
- [ Stock::CANCEL, Dialog::RESPONSE_REJECT ]
- )
- hbox = HBox.new(false, 5)
-
- hbox.pack_start(Label.new("Location:"), false)
- hbox.pack_start(location_input = Entry.new)
- location_input.width_chars = 60
- location_input.text = @location || ''
-
- dialog.vbox.pack_start(hbox, false)
-
- dialog.signal_connect(:'key-press-event', &DEFAULT_DIALOG_KEY_PRESS_HANDLER)
- dialog.show_all
- dialog.run do |response|
- if response == Dialog::RESPONSE_ACCEPT
- return @location = location_input.text
- end
- end
- return
- ensure
- dialog.destroy if dialog
- end
- end
-
- class << self
- # Starts a JSON Editor. If a block was given, it yields
- # to the JSON::Editor::MainWindow instance.
- def start(encoding = 'utf8') # :yield: window
- Gtk.init
- @window = Editor::MainWindow.new(encoding)
- @window.icon_list = [ Editor.fetch_icon('json') ]
- yield @window if block_given?
- @window.show_all
- Gtk.main
- end
-
- # Edit the string _json_ with encoding _encoding_ in the editor.
- def edit(json, encoding = 'utf8')
- start(encoding) do |window|
- window.edit json
- end
- end
-
- attr_reader :window
- end
- end
-end
- # vim: set et sw=2 ts=2:
diff --git a/ext/json/lib/json/ext.rb b/ext/json/lib/json/ext.rb
index ff4fa42329..5bacc5e371 100644
--- a/ext/json/lib/json/ext.rb
+++ b/ext/json/lib/json/ext.rb
@@ -1,13 +1,45 @@
+# frozen_string_literal: true
+
require 'json/common'
module JSON
# This module holds all the modules/classes that implement JSON's
# functionality as C extensions.
module Ext
+ class Parser
+ class << self
+ def parse(...)
+ new(...).parse
+ end
+ alias_method :parse, :parse # Allow redefinition by extensions
+ end
+
+ def initialize(source, opts = nil)
+ @source = source
+ @config = Config.new(opts)
+ end
+
+ def source
+ @source.dup
+ end
+
+ def parse
+ @config.parse(@source)
+ end
+ end
+
require 'json/ext/parser'
- require 'json/ext/generator'
- $DEBUG and warn "Using c extension for JSON."
- JSON.parser = Parser
- JSON.generator = Generator
+ Ext::Parser::Config = Ext::ParserConfig
+ JSON.parser = Ext::Parser
+
+ if RUBY_ENGINE == 'truffleruby'
+ require 'json/truffle_ruby/generator'
+ JSON.generator = JSON::TruffleRuby::Generator
+ else
+ require 'json/ext/generator'
+ JSON.generator = Generator
+ end
end
+
+ JSON_LOADED = true unless defined?(JSON::JSON_LOADED)
end
diff --git a/ext/json/lib/json/ext/generator/state.rb b/ext/json/lib/json/ext/generator/state.rb
new file mode 100644
index 0000000000..e4f425af6a
--- /dev/null
+++ b/ext/json/lib/json/ext/generator/state.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+
+module JSON
+ module Ext
+ module Generator
+ class State
+ # call-seq: new(opts = {})
+ #
+ # Instantiates a new State object, configured by _opts_.
+ #
+ # Argument +opts+, if given, contains a \Hash of options for the generation.
+ # See {Generating Options}[rdoc-ref:JSON@Generating+Options].
+ def initialize(opts = nil)
+ if opts && !opts.empty?
+ configure(opts)
+ end
+ end
+
+ # call-seq: configure(opts)
+ #
+ # Configure this State instance with the Hash _opts_, and return
+ # itself.
+ def configure(opts)
+ unless opts.is_a?(Hash)
+ if opts.respond_to?(:to_hash)
+ opts = opts.to_hash
+ elsif opts.respond_to?(:to_h)
+ opts = opts.to_h
+ else
+ raise TypeError, "can't convert #{opts.class} into Hash"
+ end
+ end
+ _configure(opts)
+ end
+
+ alias_method :merge, :configure
+
+ # call-seq: to_h
+ #
+ # Returns the configuration instance variables as a hash, that can be
+ # passed to the configure method.
+ def to_h
+ result = {
+ indent: indent,
+ space: space,
+ space_before: space_before,
+ object_nl: object_nl,
+ array_nl: array_nl,
+ as_json: as_json,
+ allow_nan: allow_nan?,
+ ascii_only: ascii_only?,
+ max_nesting: max_nesting,
+ script_safe: script_safe?,
+ strict: strict?,
+ depth: depth,
+ buffer_initial_length: buffer_initial_length,
+ }
+
+ allow_duplicate_key = allow_duplicate_key?
+ unless allow_duplicate_key.nil?
+ result[:allow_duplicate_key] = allow_duplicate_key
+ end
+
+ instance_variables.each do |iv|
+ iv = iv.to_s[1..-1]
+ result[iv.to_sym] = self[iv]
+ end
+
+ result
+ end
+
+ alias_method :to_hash, :to_h
+
+ # call-seq: [](name)
+ #
+ # Returns the value returned by method +name+.
+ def [](name)
+ ::JSON.deprecation_warning("JSON::State#[] is deprecated and will be removed in json 3.0.0")
+
+ if respond_to?(name)
+ __send__(name)
+ else
+ instance_variable_get("@#{name}") if
+ instance_variables.include?("@#{name}".to_sym) # avoid warning
+ end
+ end
+
+ # call-seq: []=(name, value)
+ #
+ # Sets the attribute name to value.
+ def []=(name, value)
+ ::JSON.deprecation_warning("JSON::State#[]= is deprecated and will be removed in json 3.0.0")
+
+ if respond_to?(name_writer = "#{name}=")
+ __send__ name_writer, value
+ else
+ instance_variable_set "@#{name}", value
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/ext/json/lib/json/generic_object.rb b/ext/json/lib/json/generic_object.rb
new file mode 100644
index 0000000000..5c8ace354b
--- /dev/null
+++ b/ext/json/lib/json/generic_object.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+begin
+ require 'ostruct'
+rescue LoadError
+ warn "JSON::GenericObject requires 'ostruct'. Please install it with `gem install ostruct`."
+end
+
+module JSON
+ class GenericObject < OpenStruct
+ class << self
+ alias [] new
+
+ def json_creatable?
+ @json_creatable
+ end
+
+ attr_writer :json_creatable
+
+ def json_create(data)
+ data = data.dup
+ data.delete JSON.create_id
+ self[data]
+ end
+
+ def from_hash(object)
+ case
+ when object.respond_to?(:to_hash)
+ result = new
+ object.to_hash.each do |key, value|
+ result[key] = from_hash(value)
+ end
+ result
+ when object.respond_to?(:to_ary)
+ object.to_ary.map { |a| from_hash(a) }
+ else
+ object
+ end
+ end
+
+ def load(source, proc = nil, opts = {})
+ result = ::JSON.load(source, proc, opts.merge(:object_class => self))
+ result.nil? ? new : result
+ end
+
+ def dump(obj, *args)
+ ::JSON.dump(obj, *args)
+ end
+ end
+ self.json_creatable = false
+
+ def to_hash
+ table
+ end
+
+ def |(other)
+ self.class[other.to_hash.merge(to_hash)]
+ end
+
+ def as_json(*)
+ { JSON.create_id => self.class.name }.merge to_hash
+ end
+
+ def to_json(*a)
+ as_json.to_json(*a)
+ end
+ end if defined?(::OpenStruct)
+end
diff --git a/ext/json/lib/json/json.xpm b/ext/json/lib/json/json.xpm
deleted file mode 100644
index 2cb626bb05..0000000000
--- a/ext/json/lib/json/json.xpm
+++ /dev/null
@@ -1,1499 +0,0 @@
-/* XPM */
-static char * json_xpm[] = {
-"64 64 1432 2",
-" c None",
-". c #641839",
-"+ c #CF163C",
-"@ c #D31C3B",
-"# c #E11A38",
-"$ c #5F242D",
-"% c #320C22",
-"& c #9B532D",
-"* c #F32E34",
-"= c #820F33",
-"- c #4B0F34",
-"; c #8E1237",
-"> c #944029",
-", c #961325",
-"' c #A00C24",
-") c #872C23",
-"! c #694021",
-"~ c #590D1F",
-"{ c #420528",
-"] c #D85A2D",
-"^ c #7E092B",
-"/ c #0E0925",
-"( c #0D081F",
-"_ c #0F081E",
-": c #12071F",
-"< c #360620",
-"[ c #682A21",
-"} c #673F21",
-"| c #780E21",
-"1 c #A82320",
-"2 c #8D1D1F",
-"3 c #970127",
-"4 c #0D0123",
-"5 c #0D0324",
-"6 c #3B1E28",
-"7 c #C28429",
-"8 c #0C0523",
-"9 c #0C041E",
-"0 c #0E031A",
-"a c #11031A",
-"b c #13031B",
-"c c #13031C",
-"d c #11031D",
-"e c #19051E",
-"f c #390E20",
-"g c #9C0C20",
-"h c #C00721",
-"i c #980320",
-"j c #14031E",
-"k c #CD9F32",
-"l c #C29F2E",
-"m c #0F0325",
-"n c #0D0321",
-"o c #0E0324",
-"p c #D08329",
-"q c #9D1B27",
-"r c #1C0320",
-"s c #0D011A",
-"t c #120117",
-"u c #130017",
-"v c #150018",
-"w c #160119",
-"x c #17021A",
-"y c #15021B",
-"z c #11021E",
-"A c #0F021F",
-"B c #8C1821",
-"C c #CF4522",
-"D c #831821",
-"E c #BA7033",
-"F c #EDB339",
-"G c #C89733",
-"H c #280727",
-"I c #0F051F",
-"J c #0E0420",
-"K c #591F27",
-"L c #E47129",
-"M c #612224",
-"N c #0C021D",
-"O c #120018",
-"P c #140017",
-"Q c #170017",
-"R c #190018",
-"S c #1B0019",
-"T c #1B011A",
-"U c #18011B",
-"V c #15011C",
-"W c #12031E",
-"X c #460A21",
-"Y c #A13823",
-"Z c #784323",
-"` c #5A0C21",
-" . c #BC4530",
-".. c #EB5B38",
-"+. c #CE4E3B",
-"@. c #DD9334",
-"#. c #751A27",
-"$. c #11071E",
-"%. c #0F041C",
-"&. c #1E0824",
-"*. c #955A28",
-"=. c #9A5027",
-"-. c #1E0321",
-";. c #11011A",
-">. c #140018",
-",. c #180018",
-"'. c #1F001A",
-"). c #20001B",
-"!. c #1E001A",
-"~. c #1B001A",
-"{. c #16021B",
-"]. c #16041E",
-"^. c #220622",
-"/. c #5F3525",
-"(. c #DE5724",
-"_. c #611021",
-":. c #0F0925",
-"<. c #D1892E",
-"[. c #F27036",
-"}. c #EC633B",
-"|. c #DA293C",
-"1. c #E64833",
-"2. c #912226",
-"3. c #11081C",
-"4. c #110419",
-"5. c #0F041E",
-"6. c #451425",
-"7. c #BF6F28",
-"8. c #332225",
-"9. c #0E021E",
-"0. c #13001B",
-"a. c #17001A",
-"b. c #1C001B",
-"c. c #21001C",
-"d. c #23001C",
-"e. c #21001B",
-"f. c #19021A",
-"g. c #17041E",
-"h. c #150721",
-"i. c #602424",
-"j. c #D51223",
-"k. c #540820",
-"l. c #D04D2D",
-"m. c #EA8933",
-"n. c #875637",
-"o. c #88543A",
-"p. c #E5923A",
-"q. c #891931",
-"r. c #130B25",
-"s. c #10051B",
-"t. c #110217",
-"u. c #12021A",
-"v. c #761826",
-"w. c #E2A728",
-"x. c #300224",
-"y. c #10011E",
-"z. c #16001B",
-"A. c #1B001B",
-"B. c #21001A",
-"C. c #1E0019",
-"D. c #1D0019",
-"E. c #1A011A",
-"F. c #17031C",
-"G. c #120720",
-"H. c #4E0822",
-"I. c #670721",
-"J. c #C07630",
-"K. c #F59734",
-"L. c #BE1B35",
-"M. c #0E1435",
-"N. c #522037",
-"O. c #DB8039",
-"P. c #D45933",
-"Q. c #420927",
-"R. c #0F041D",
-"S. c #140118",
-"T. c #13021D",
-"U. c #100423",
-"V. c #7B6227",
-"W. c #C04326",
-"X. c #0E0020",
-"Y. c #13001D",
-"Z. c #18001B",
-"`. c #1E001B",
-" + c #22001C",
-".+ c #22001B",
-"++ c #1B011B",
-"@+ c #16041D",
-"#+ c #130520",
-"$+ c #860521",
-"%+ c #710520",
-"&+ c #670A2A",
-"*+ c #A66431",
-"=+ c #E97536",
-"-+ c #F8833A",
-";+ c #F77A3A",
-">+ c #C45337",
-",+ c #0A1C35",
-"'+ c #993638",
-")+ c #F7863B",
-"!+ c #F49736",
-"~+ c #94462B",
-"{+ c #0E031F",
-"]+ c #130119",
-"^+ c #160018",
-"/+ c #16011B",
-"(+ c #15021F",
-"_+ c #120123",
-":+ c #A65C28",
-"<+ c #5C4D23",
-"[+ c #0F001F",
-"}+ c #14001D",
-"|+ c #1A001B",
-"1+ c #1F001B",
-"2+ c #24001D",
-"3+ c #25001D",
-"4+ c #24001C",
-"5+ c #1F001C",
-"6+ c #1A011C",
-"7+ c #16021E",
-"8+ c #3F0421",
-"9+ c #BC0522",
-"0+ c #1C041E",
-"a+ c #7F5531",
-"b+ c #E68A38",
-"c+ c #F8933E",
-"d+ c #FA7942",
-"e+ c #FB7543",
-"f+ c #FA6F41",
-"g+ c #F1793D",
-"h+ c #7D3B3A",
-"i+ c #28263B",
-"j+ c #D45441",
-"k+ c #F8A238",
-"l+ c #996B2D",
-"m+ c #0E0421",
-"n+ c #12011A",
-"o+ c #180019",
-"p+ c #17001C",
-"q+ c #12001F",
-"r+ c #4C2B2A",
-"s+ c #DB8130",
-"t+ c #540023",
-"u+ c #0F0120",
-"v+ c #16011C",
-"w+ c #22001D",
-"x+ c #25001F",
-"y+ c #26001F",
-"z+ c #25001E",
-"A+ c #24001E",
-"B+ c #1D001C",
-"C+ c #18011D",
-"D+ c #16031F",
-"E+ c #3C0522",
-"F+ c #9B0821",
-"G+ c #13041E",
-"H+ c #F6462E",
-"I+ c #E6AB37",
-"J+ c #E7A03E",
-"K+ c #FA9F44",
-"L+ c #FB8A48",
-"M+ c #FD7A4A",
-"N+ c #FD794A",
-"O+ c #FD7748",
-"P+ c #FD7E45",
-"Q+ c #FD8343",
-"R+ c #FB5D42",
-"S+ c #6E3A40",
-"T+ c #EE8A37",
-"U+ c #7E252B",
-"V+ c #100520",
-"W+ c #13011A",
-"X+ c #170019",
-"Y+ c #15001C",
-"Z+ c #0F0020",
-"`+ c #564427",
-" @ c #E0BA29",
-".@ c #5E2B25",
-"+@ c #10011F",
-"@@ c #17011C",
-"#@ c #1E001D",
-"$@ c #23001F",
-"%@ c #250020",
-"&@ c #24001F",
-"*@ c #23001E",
-"=@ c #21001E",
-"-@ c #1B001C",
-";@ c #17021D",
-">@ c #14041E",
-",@ c #AC0B25",
-"'@ c #5E1420",
-")@ c #F28635",
-"!@ c #C2733E",
-"~@ c #984C44",
-"{@ c #EA9148",
-"]@ c #FB844B",
-"^@ c #FD7E4C",
-"/@ c #FE7E4C",
-"(@ c #FE7E4B",
-"_@ c #FE7749",
-":@ c #FD7148",
-"<@ c #FB7D46",
-"[@ c #F89641",
-"}@ c #B95634",
-"|@ c #0D0927",
-"1@ c #11041D",
-"2@ c #150119",
-"3@ c #180017",
-"4@ c #16001A",
-"5@ c #13001E",
-"6@ c #110023",
-"7@ c #944C29",
-"8@ c #EE6229",
-"9@ c #3D0324",
-"0@ c #12021F",
-"a@ c #19011D",
-"b@ c #21001F",
-"c@ c #22001F",
-"d@ c #20001E",
-"e@ c #1F001D",
-"f@ c #1C001C",
-"g@ c #19011C",
-"h@ c #3D1621",
-"i@ c #B53622",
-"j@ c #31061F",
-"k@ c #841D34",
-"l@ c #F2703F",
-"m@ c #C14445",
-"n@ c #E67349",
-"o@ c #FB8E4B",
-"p@ c #FD834C",
-"q@ c #FE834D",
-"r@ c #FE834C",
-"s@ c #FE804C",
-"t@ c #FD814B",
-"u@ c #FB7D49",
-"v@ c #F79B43",
-"w@ c #AF1234",
-"x@ c #0D0625",
-"y@ c #13021C",
-"z@ c #1A0019",
-"A@ c #190019",
-"B@ c #410225",
-"C@ c #D39729",
-"D@ c #AA5927",
-"E@ c #0E0422",
-"F@ c #15021E",
-"G@ c #1A011D",
-"H@ c #1D001D",
-"I@ c #15031D",
-"J@ c #240820",
-"K@ c #A01023",
-"L@ c #670B21",
-"M@ c #3D0D33",
-"N@ c #E63C3E",
-"O@ c #EF7C45",
-"P@ c #F59048",
-"Q@ c #FB944A",
-"R@ c #FD904A",
-"S@ c #FE8E4B",
-"T@ c #FE854A",
-"U@ c #FE854B",
-"V@ c #FE884C",
-"W@ c #FC954B",
-"X@ c #F8AB45",
-"Y@ c #C37A35",
-"Z@ c #0D0425",
-"`@ c #13011B",
-" # c #170018",
-".# c #1A0018",
-"+# c #1C0019",
-"@# c #15001B",
-"## c #100120",
-"$# c #311F25",
-"%# c #E68E28",
-"&# c #7A1425",
-"*# c #130321",
-"=# c #17011E",
-"-# c #1A001D",
-";# c #19001B",
-"># c #16021C",
-",# c #130521",
-"'# c #6F3123",
-")# c #6D3022",
-"!# c #C89433",
-"~# c #EA7E3E",
-"{# c #DB2943",
-"]# c #EF7745",
-"^# c #FB8544",
-"/# c #FD9A43",
-"(# c #FE9941",
-"_# c #FE9D43",
-":# c #FEA548",
-"<# c #FEAE49",
-"[# c #FCB944",
-"}# c #CA9F35",
-"|# c #0E0225",
-"1# c #11001B",
-"2# c #160019",
-"3# c #12011B",
-"4# c #0F0220",
-"5# c #351D26",
-"6# c #D85B28",
-"7# c #6C0F26",
-"8# c #190121",
-"9# c #1B001E",
-"0# c #1A001C",
-"a# c #1D001B",
-"b# c #130220",
-"c# c #703A23",
-"d# c #713A23",
-"e# c #140327",
-"f# c #411B36",
-"g# c #C8713E",
-"h# c #7A3A3F",
-"i# c #CE2C3C",
-"j# c #E77338",
-"k# c #9C6535",
-"l# c #9C6233",
-"m# c #9C6332",
-"n# c #9C6A35",
-"o# c #C37D3C",
-"p# c #FEAC41",
-"q# c #FEC23E",
-"r# c #826330",
-"s# c #100122",
-"t# c #120019",
-"u# c #150017",
-"v# c #190017",
-"w# c #1B0018",
-"x# c #12001A",
-"y# c #10021F",
-"z# c #1A0326",
-"A# c #5F292A",
-"B# c #7B4E29",
-"C# c #3C0E25",
-"D# c #1A0020",
-"E# c #14021F",
-"F# c #723B23",
-"G# c #14001A",
-"H# c #58042A",
-"I# c #A28337",
-"J# c #C8813B",
-"K# c #B14B38",
-"L# c #761231",
-"M# c #5A132A",
-"N# c #0D0726",
-"O# c #0C0623",
-"P# c #0B0723",
-"Q# c #0B0A26",
-"R# c #321C2D",
-"S# c #C45B33",
-"T# c #FEBB33",
-"U# c #13052A",
-"V# c #13011F",
-"W# c #160017",
-"X# c #15001A",
-"Y# c #12001D",
-"Z# c #94062A",
-"`# c #630D2C",
-" $ c #85292B",
-".$ c #AA5E29",
-"+$ c #1F0123",
-"@$ c #19011F",
-"#$ c #1E001C",
-"$$ c #15031F",
-"%$ c #712122",
-"&$ c #712223",
-"*$ c #14011B",
-"=$ c #110321",
-"-$ c #AF0C2B",
-";$ c #E7D534",
-">$ c #EAC934",
-",$ c #84582D",
-"'$ c #1B0824",
-")$ c #11041E",
-"!$ c #10021B",
-"~$ c #100119",
-"{$ c #100218",
-"]$ c #0F041A",
-"^$ c #0E0720",
-"/$ c #2C1026",
-"($ c #D8A328",
-"_$ c #140322",
-":$ c #160016",
-"<$ c #14001F",
-"[$ c #120024",
-"}$ c #100128",
-"|$ c #3C032F",
-"1$ c #2C062E",
-"2$ c #29022B",
-"3$ c #A31D29",
-"4$ c #976A25",
-"5$ c #1A0321",
-"6$ c #17031E",
-"7$ c #1B021D",
-"8$ c #20001C",
-"9$ c #14041F",
-"0$ c #703422",
-"a$ c #6F3522",
-"b$ c #8D0328",
-"c$ c #920329",
-"d$ c #0F0326",
-"e$ c #100321",
-"f$ c #11021B",
-"g$ c #130117",
-"h$ c #140016",
-"i$ c #150015",
-"j$ c #140015",
-"k$ c #130116",
-"l$ c #120219",
-"m$ c #11031C",
-"n$ c #12031D",
-"o$ c #170016",
-"p$ c #160020",
-"q$ c #250029",
-"r$ c #670033",
-"s$ c #DCA238",
-"t$ c #F5C736",
-"u$ c #9A732E",
-"v$ c #110227",
-"w$ c #110324",
-"x$ c #811924",
-"y$ c #A04323",
-"z$ c #250721",
-"A$ c #1A041F",
-"B$ c #1E011D",
-"C$ c #1C011C",
-"D$ c #18031D",
-"E$ c #130721",
-"F$ c #6F3623",
-"G$ c #6B3622",
-"H$ c #1A001A",
-"I$ c #14011F",
-"J$ c #12011E",
-"K$ c #11011C",
-"L$ c #140117",
-"M$ c #170015",
-"N$ c #150016",
-"O$ c #120119",
-"P$ c #11011B",
-"Q$ c #11001A",
-"R$ c #130018",
-"S$ c #170118",
-"T$ c #170119",
-"U$ c #18021E",
-"V$ c #1A0126",
-"W$ c #6F2332",
-"X$ c #E5563B",
-"Y$ c #F1B83F",
-"Z$ c #F6CC38",
-"`$ c #9D7A2D",
-" % c #130123",
-".% c #130320",
-"+% c #2A0721",
-"@% c #B00E24",
-"#% c #7D0B23",
-"$% c #1F0522",
-"%% c #1E0220",
-"&% c #1D011E",
-"*% c #1A031E",
-"=% c #15051F",
-"-% c #241322",
-";% c #A32F23",
-">% c #670E21",
-",% c #1C001A",
-"'% c #19001A",
-")% c #180016",
-"!% c #160118",
-"~% c #140219",
-"{% c #11021C",
-"]% c #10021E",
-"^% c #0F011D",
-"/% c #170117",
-"(% c #160219",
-"_% c #17041D",
-":% c #190523",
-"<% c #8C042E",
-"[% c #B65838",
-"}% c #E9D73F",
-"|% c #EED43E",
-"1% c #D85538",
-"2% c #493129",
-"3% c #130120",
-"4% c #15021D",
-"5% c #330822",
-"6% c #8A0825",
-"7% c #3C0424",
-"8% c #1E0322",
-"9% c #1C0321",
-"0% c #180421",
-"a% c #130822",
-"b% c #AF2D24",
-"c% c #BC5623",
-"d% c #2F071F",
-"e% c #1A041C",
-"f% c #1C031C",
-"g% c #1D011C",
-"h% c #160117",
-"i% c #150419",
-"j% c #12081D",
-"k% c #0F0923",
-"l% c #A77027",
-"m% c #A60525",
-"n% c #11021A",
-"o% c #130218",
-"p% c #150319",
-"q% c #16061D",
-"r% c #180923",
-"s% c #9C1D2B",
-"t% c #A32636",
-"u% c #A66E3B",
-"v% c #4B2E3C",
-"w% c #412C36",
-"x% c #36012D",
-"y% c #140123",
-"z% c #17001E",
-"A% c #19011B",
-"B% c #1A0421",
-"C% c #340425",
-"D% c #9E0326",
-"E% c #1F0424",
-"F% c #1C0524",
-"G% c #180724",
-"H% c #A91024",
-"I% c #D55D24",
-"J% c #90071E",
-"K% c #3C051D",
-"L% c #1C021C",
-"M% c #1C011A",
-"N% c #1D001A",
-"O% c #160116",
-"P% c #150216",
-"Q% c #140217",
-"R% c #140618",
-"S% c #120D1D",
-"T% c #231925",
-"U% c #B16A2E",
-"V% c #FDAC34",
-"W% c #D58631",
-"X% c #280E2A",
-"Y% c #0D0A23",
-"Z% c #0F0920",
-"`% c #120C21",
-" & c #1F1026",
-".& c #A3352E",
-"+& c #EE9F36",
-"@& c #5D2A3C",
-"#& c #960D3C",
-"$& c #970638",
-"%& c #A00330",
-"&& c #4D0126",
-"*& c #1C001F",
-"=& c #280120",
-"-& c #290223",
-";& c #1F0425",
-">& c #260726",
-",& c #340A26",
-"'& c #850925",
-")& c #3A0823",
-"!& c #82071D",
-"~& c #5E071D",
-"{& c #18051C",
-"]& c #18021A",
-"^& c #190118",
-"/& c #160217",
-"(& c #150418",
-"_& c #130618",
-":& c #110718",
-"<& c #10081A",
-"[& c #110D1D",
-"}& c #291C24",
-"|& c #A73B2D",
-"1& c #FD6B36",
-"2& c #FD853C",
-"3& c #FD863B",
-"4& c #C24A35",
-"5& c #6B442F",
-"6& c #6D302D",
-"7& c #6E252E",
-"8& c #8E3B32",
-"9& c #DE7739",
-"0& c #F48E3F",
-"a& c #DD8D41",
-"b& c #854F3D",
-"c& c #7E2D35",
-"d& c #33082B",
-"e& c #1C0222",
-"f& c #20001F",
-"g& c #1F0222",
-"h& c #1A0524",
-"i& c #440C27",
-"j& c #BC1427",
-"k& c #20041B",
-"l& c #53061C",
-"m& c #25071B",
-"n& c #11061A",
-"o& c #130418",
-"p& c #140317",
-"q& c #150217",
-"r& c #160318",
-"s& c #12051B",
-"t& c #100C1D",
-"u& c #0E101E",
-"v& c #0C121F",
-"w& c #0C1321",
-"x& c #781725",
-"y& c #B25D2C",
-"z& c #FA6335",
-"A& c #FD633C",
-"B& c #FE6D42",
-"C& c #FE7C42",
-"D& c #FE813F",
-"E& c #FE873C",
-"F& c #FD743B",
-"G& c #FB683B",
-"H& c #FA7A3E",
-"I& c #F98242",
-"J& c #F97844",
-"K& c #F98943",
-"L& c #F79C3D",
-"M& c #A25133",
-"N& c #280B28",
-"O& c #1D021F",
-"P& c #1F011C",
-"Q& c #280321",
-"R& c #1C0724",
-"S& c #3F1C27",
-"T& c #D33C27",
-"U& c #0E061B",
-"V& c #0C091C",
-"W& c #0C0A1B",
-"X& c #0E091A",
-"Y& c #11081B",
-"Z& c #100A20",
-"`& c #0E0D23",
-" * c #551227",
-".* c #B21829",
-"+* c #C42329",
-"@* c #C62C29",
-"#* c #C55429",
-"$* c #E76F2B",
-"%* c #F14232",
-"&* c #F95E3A",
-"** c #FC6740",
-"=* c #FE6E45",
-"-* c #FE7246",
-";* c #FE7545",
-">* c #FE7744",
-",* c #FD7745",
-"'* c #FD7845",
-")* c #FD7847",
-"!* c #FD7948",
-"~* c #FD7B44",
-"{* c #FC7C3B",
-"]* c #6F3130",
-"^* c #140B24",
-"/* c #19031D",
-"(* c #1C011B",
-"_* c #5A011F",
-":* c #B70421",
-"<* c #380824",
-"[* c #3E2626",
-"}* c #9F5626",
-"|* c #13051E",
-"1* c #360A21",
-"2* c #361223",
-"3* c #371724",
-"4* c #381824",
-"5* c #3B1524",
-"6* c #3E1E26",
-"7* c #471A29",
-"8* c #DB252E",
-"9* c #ED2733",
-"0* c #EE5436",
-"a* c #F04237",
-"b* c #F33934",
-"c* c #F53D2F",
-"d* c #D7312B",
-"e* c #AF212B",
-"f* c #3A2C31",
-"g* c #F65F39",
-"h* c #FB6F41",
-"i* c #FD6D45",
-"j* c #FE7047",
-"k* c #FE7647",
-"l* c #FE7847",
-"m* c #FE7848",
-"n* c #FE7748",
-"o* c #FE7948",
-"p* c #FE7C48",
-"q* c #FE7C47",
-"r* c #FE7642",
-"s* c #FE7439",
-"t* c #6D332C",
-"u* c #100B21",
-"v* c #16031B",
-"w* c #2B001B",
-"x* c #22011F",
-"y* c #220521",
-"z* c #1B0A23",
-"A* c #421425",
-"B* c #951924",
-"C* c #381023",
-"D* c #E94028",
-"E* c #E7302B",
-"F* c #EF432D",
-"G* c #F4302E",
-"H* c #F32C30",
-"I* c #CB4432",
-"J* c #DD3235",
-"K* c #EF4B3A",
-"L* c #F0333E",
-"M* c #CC3D3F",
-"N* c #E4313C",
-"O* c #F34834",
-"P* c #D13E2C",
-"Q* c #431825",
-"R* c #0E1424",
-"S* c #3C202C",
-"T* c #F15537",
-"U* c #F97140",
-"V* c #FC6E45",
-"W* c #FE7547",
-"X* c #FE7947",
-"Y* c #FE7B48",
-"Z* c #FE7D48",
-"`* c #FE8047",
-" = c #FE7A42",
-".= c #FE7A38",
-"+= c #6D442B",
-"@= c #0F0B21",
-"#= c #15031A",
-"$= c #49001B",
-"%= c #2F001C",
-"&= c #21021E",
-"*= c #220620",
-"== c #1B0D23",
-"-= c #641625",
-";= c #951823",
-">= c #390F25",
-",= c #AC3A2A",
-"'= c #B6492E",
-")= c #ED7531",
-"!= c #F45A34",
-"~= c #F54C36",
-"{= c #C72D39",
-"]= c #DE283C",
-"^= c #F33B40",
-"/= c #F34142",
-"(= c #D0393F",
-"_= c #E72E39",
-":= c #DB3C2E",
-"<= c #461724",
-"[= c #0F0D1E",
-"}= c #140B1E",
-"|= c #341427",
-"1= c #CB4834",
-"2= c #F7743F",
-"3= c #FB7145",
-"4= c #FE7747",
-"5= c #FE7A47",
-"6= c #FF7B48",
-"7= c #FF7C48",
-"8= c #FE7F47",
-"9= c #FE8247",
-"0= c #FE8642",
-"a= c #FE8439",
-"b= c #6D442D",
-"c= c #0F0A21",
-"d= c #14031A",
-"e= c #20031D",
-"f= c #210821",
-"g= c #191024",
-"h= c #CC1C25",
-"i= c #961423",
-"j= c #2C162C",
-"k= c #BD242E",
-"l= c #EF2C31",
-"m= c #F54C34",
-"n= c #F34037",
-"o= c #F5353A",
-"p= c #F7413D",
-"q= c #F8423D",
-"r= c #F93A39",
-"s= c #F95731",
-"t= c #341425",
-"u= c #110A1D",
-"v= c #140619",
-"w= c #18051B",
-"x= c #200F26",
-"y= c #864833",
-"z= c #F8773F",
-"A= c #FC7445",
-"B= c #FF7E48",
-"C= c #FF7E49",
-"D= c #FF7D49",
-"E= c #FF7D48",
-"F= c #FE8347",
-"G= c #FE8743",
-"H= c #FE893B",
-"I= c #6E452F",
-"J= c #100E23",
-"K= c #14041A",
-"L= c #55041D",
-"M= c #540921",
-"N= c #161124",
-"O= c #CE6A25",
-"P= c #3F1129",
-"Q= c #170A29",
-"R= c #0F0F29",
-"S= c #15132B",
-"T= c #1E182D",
-"U= c #A82B3D",
-"V= c #CB6633",
-"W= c #CC6932",
-"X= c #CC3D2D",
-"Y= c #331225",
-"Z= c #0F091C",
-"`= c #120417",
-" - c #160216",
-".- c #190419",
-"+- c #210F26",
-"@- c #8C4934",
-"#- c #F97A40",
-"$- c #FC7545",
-"%- c #FF7B49",
-"&- c #FE7D46",
-"*- c #FE7E43",
-"=- c #FD7B3E",
-"-- c #FA6934",
-";- c #532328",
-">- c #130B1D",
-",- c #150519",
-"'- c #14041C",
-")- c #120920",
-"!- c #C43624",
-"~- c #A21E23",
-"{- c #F87C30",
-"]- c #C9302D",
-"^- c #300F2A",
-"/- c #591129",
-"(- c #171328",
-"_- c #171628",
-":- c #141829",
-"<- c #101A2B",
-"[- c #0F172B",
-"}- c #0F1226",
-"|- c #0E0C20",
-"1- c #100619",
-"2- c #140316",
-"3- c #19051B",
-"4- c #3C1428",
-"5- c #E04B36",
-"6- c #FA7B41",
-"7- c #FD7346",
-"8- c #FE7548",
-"9- c #FF7849",
-"0- c #FF7749",
-"a- c #FE7B47",
-"b- c #FE7945",
-"c- c #FC7740",
-"d- c #FA7E39",
-"e- c #C1432F",
-"f- c #131523",
-"g- c #130A1C",
-"h- c #420621",
-"i- c #D08423",
-"j- c #F87739",
-"k- c #C03D37",
-"l- c #962B34",
-"m- c #A14332",
-"n- c #E54B30",
-"o- c #9E3E2F",
-"p- c #7F262E",
-"q- c #922D2E",
-"r- c #9C4B2E",
-"s- c #65212C",
-"t- c #101628",
-"u- c #101022",
-"v- c #11091C",
-"w- c #130619",
-"x- c #160A1E",
-"y- c #43252C",
-"z- c #F66439",
-"A- c #FA6942",
-"B- c #FD6C47",
-"C- c #FE6E48",
-"D- c #FE6F48",
-"E- c #FE7049",
-"F- c #FE714A",
-"G- c #FE744A",
-"H- c #FE7846",
-"I- c #FD7243",
-"J- c #FC703E",
-"K- c #FA6C37",
-"L- c #81312B",
-"M- c #121123",
-"N- c #15071D",
-"O- c #16031A",
-"P- c #17021B",
-"Q- c #8F3D22",
-"R- c #F8393E",
-"S- c #E42A3D",
-"T- c #E7473B",
-"U- c #FB503B",
-"V- c #FB4F3A",
-"W- c #F95439",
-"X- c #ED4C38",
-"Y- c #F45938",
-"Z- c #FB6537",
-"`- c #EA5236",
-" ; c #CE6232",
-".; c #CD392C",
-"+; c #181425",
-"@; c #120F21",
-"#; c #130D20",
-"$; c #151225",
-"%; c #903431",
-"&; c #F8703D",
-"*; c #FB6344",
-"=; c #FD6748",
-"-; c #FE6849",
-";; c #FE6949",
-">; c #FE6A49",
-",; c #FE6C4A",
-"'; c #FE704A",
-"); c #FE734A",
-"!; c #FE7449",
-"~; c #FE7347",
-"{; c #FE7145",
-"]; c #FD6C42",
-"^; c #FD753D",
-"/; c #F36E35",
-"(; c #CB452C",
-"_; c #600D24",
-":; c #1C061F",
-"<; c #1E031F",
-"[; c #5B3821",
-"}; c #CE9822",
-"|; c #FA4341",
-"1; c #FB4341",
-"2; c #FC4541",
-"3; c #FC4542",
-"4; c #FC4143",
-"5; c #FC4D42",
-"6; c #FB5042",
-"7; c #FB5342",
-"8; c #FC5242",
-"9; c #FD4F40",
-"0; c #FD503E",
-"a; c #FB6339",
-"b; c #F45E33",
-"c; c #A12A2E",
-"d; c #401E2C",
-"e; c #452D2F",
-"f; c #F74F38",
-"g; c #FA5940",
-"h; c #FC6245",
-"i; c #FE6447",
-"j; c #FE6449",
-"k; c #FE6549",
-"l; c #FE6749",
-"m; c #FE6B49",
-"n; c #FE6D49",
-"o; c #FE6D48",
-"p; c #FE6D47",
-"q; c #FE6D45",
-"r; c #FE6C44",
-"s; c #FE6A42",
-"t; c #FE663C",
-"u; c #FC6233",
-"v; c #752129",
-"w; c #1F0922",
-"x; c #750520",
-"y; c #81061F",
-"z; c #FA3D42",
-"A; c #FB4142",
-"B; c #FD4543",
-"C; c #FD4844",
-"D; c #FD4A45",
-"E; c #FD4D45",
-"F; c #FD5045",
-"G; c #FD5345",
-"H; c #FE5346",
-"I; c #FE5445",
-"J; c #FD5444",
-"K; c #FC4F41",
-"L; c #FA513D",
-"M; c #F95339",
-"N; c #F63736",
-"O; c #F75737",
-"P; c #F95F3B",
-"Q; c #FB5840",
-"R; c #FD5F43",
-"S; c #FE6345",
-"T; c #FE6547",
-"U; c #FE6548",
-"V; c #FE6448",
-"W; c #FE6248",
-"X; c #FE6348",
-"Y; c #FE6748",
-"Z; c #FE6848",
-"`; c #FE6846",
-" > c #FE6A45",
-".> c #FE6D43",
-"+> c #FE703F",
-"@> c #FC6F36",
-"#> c #6F302B",
-"$> c #140A22",
-"%> c #FA3B42",
-"&> c #FC4243",
-"*> c #FD4744",
-"=> c #FE4A45",
-"-> c #FE4C47",
-";> c #FE4D47",
-">> c #FE5047",
-",> c #FE5347",
-"'> c #FE5447",
-")> c #FD5246",
-"!> c #FB503F",
-"~> c #FA543D",
-"{> c #9B3D3B",
-"]> c #A3433B",
-"^> c #F9683D",
-"/> c #FC6940",
-"(> c #FE6342",
-"_> c #FE6645",
-":> c #FE6646",
-"<> c #FE6147",
-"[> c #FE6048",
-"}> c #FE6148",
-"|> c #FE6746",
-"1> c #FE6A46",
-"2> c #FE6F45",
-"3> c #FE7441",
-"4> c #FC7D39",
-"5> c #6C422E",
-"6> c #0F0F23",
-"7> c #FA4142",
-"8> c #FC4643",
-"9> c #FE4D46",
-"0> c #FE4E47",
-"a> c #FE4F48",
-"b> c #FE5148",
-"c> c #FE5348",
-"d> c #FE5548",
-"e> c #FE5247",
-"f> c #FD5445",
-"g> c #FC5544",
-"h> c #F96041",
-"i> c #D33F3D",
-"j> c #392D39",
-"k> c #973C38",
-"l> c #F94E3A",
-"m> c #FD693E",
-"n> c #FE6C43",
-"o> c #FE6047",
-"p> c #FE5D47",
-"q> c #FE5E48",
-"r> c #FE6948",
-"s> c #FE6947",
-"t> c #FE6B47",
-"u> c #FE6E46",
-"v> c #FD6D43",
-"w> c #FB723D",
-"x> c #D54A33",
-"y> c #301C29",
-"z> c #FB4A42",
-"A> c #FD4B44",
-"B> c #FE4F47",
-"C> c #FE5048",
-"D> c #FE5648",
-"E> c #FE5848",
-"F> c #FE5747",
-"G> c #FE5547",
-"H> c #FC5945",
-"I> c #F95742",
-"J> c #F3543D",
-"K> c #A33336",
-"L> c #302032",
-"M> c #152433",
-"N> c #CD3E38",
-"O> c #FD5A3F",
-"P> c #FE6343",
-"Q> c #FE6446",
-"R> c #FE6247",
-"S> c #FE6A47",
-"T> c #FC6542",
-"U> c #FB6A3B",
-"V> c #FA6D34",
-"W> c #D73C2D",
-"X> c #442428",
-"Y> c #281323",
-"Z> c #FD4E42",
-"`> c #FD4D43",
-" , c #FE4D45",
-"., c #FE5248",
-"+, c #FE5947",
-"@, c #FE5C47",
-"#, c #FE5B47",
-"$, c #FE5A47",
-"%, c #FE5847",
-"&, c #FC5C45",
-"*, c #F95B43",
-"=, c #F3613F",
-"-, c #E74F37",
-";, c #8C2431",
-">, c #161E2F",
-",, c #CD4E33",
-"', c #FD503A",
-"), c #FE5D40",
-"!, c #FE6445",
-"~, c #FE6946",
-"{, c #FE6847",
-"], c #FE6747",
-"^, c #FD6644",
-"/, c #FD6241",
-"(, c #FD5B3D",
-"_, c #FE6739",
-":, c #FE6135",
-"<, c #AB4830",
-"[, c #733E2A",
-"}, c #161224",
-"|, c #FC4E42",
-"1, c #FE4D44",
-"2, c #FE4E46",
-"3, c #FE5147",
-"4, c #FE5E47",
-"5, c #FD5C46",
-"6, c #FA5B44",
-"7, c #F45441",
-"8, c #EB393A",
-"9, c #CC3433",
-"0, c #47212F",
-"a, c #59242F",
-"b, c #FC6734",
-"c, c #FC6F3A",
-"d, c #FC723E",
-"e, c #FD6540",
-"f, c #FE6442",
-"g, c #FE6643",
-"h, c #FE6944",
-"i, c #FE6546",
-"j, c #FE6444",
-"k, c #FE6143",
-"l, c #FE5E41",
-"m, c #FE613F",
-"n, c #FE683C",
-"o, c #FE7937",
-"p, c #A25030",
-"q, c #692629",
-"r, c #151122",
-"s, c #FA573F",
-"t, c #FB4D40",
-"u, c #FC4F43",
-"v, c #FE5246",
-"w, c #FF6347",
-"x, c #FE5F48",
-"y, c #F65942",
-"z, c #F0493D",
-"A, c #ED3736",
-"B, c #73262F",
-"C, c #10152C",
-"D, c #3B292F",
-"E, c #363034",
-"F, c #AC3938",
-"G, c #FC6B3B",
-"H, c #FD763C",
-"I, c #FE6D3F",
-"J, c #FE6341",
-"K, c #FE6642",
-"L, c #FE6745",
-"M, c #FE6245",
-"N, c #FE6244",
-"O, c #FE6841",
-"P, c #FF683B",
-"Q, c #EC7035",
-"R, c #D0412D",
-"S, c #3A1627",
-"T, c #CF3938",
-"U, c #F6543C",
-"V, c #FB5040",
-"W, c #FD5544",
-"X, c #FE5A48",
-"Y, c #FE5D48",
-"Z, c #FE5F47",
-"`, c #FF6147",
-" ' c #FD5C45",
-".' c #FB5B43",
-"+' c #FA5A42",
-"@' c #F76040",
-"#' c #F4623D",
-"$' c #F26D38",
-"%' c #EC4130",
-"&' c #380E2B",
-"*' c #13122C",
-"=' c #362D31",
-"-' c #353435",
-";' c #352E37",
-">' c #2D3337",
-",' c #CC5838",
-"'' c #CD6F3A",
-")' c #CE6E3D",
-"!' c #FE793F",
-"~' c #FD7541",
-"{' c #FD6243",
-"]' c #FE6545",
-"^' c #FF6543",
-"/' c #FF6240",
-"(' c #FE723B",
-"_' c #FE8034",
-":' c #442D2C",
-"<' c #311725",
-"[' c #222830",
-"}' c #B73B36",
-"|' c #F94C3D",
-"1' c #FD5543",
-"2' c #FE5B48",
-"3' c #FF5E47",
-"4' c #FE5C48",
-"5' c #FC5B44",
-"6' c #F95640",
-"7' c #C34E3D",
-"8' c #A45A3A",
-"9' c #F37438",
-"0' c #F28935",
-"a' c #AF422F",
-"b' c #240D2B",
-"c' c #88292F",
-"d' c #FA8E34",
-"e' c #FC7E38",
-"f' c #FC5939",
-"g' c #694A37",
-"h' c #693437",
-"i' c #382638",
-"j' c #142439",
-"k' c #9F483A",
-"l' c #C45E3C",
-"m' c #FD7240",
-"n' c #FF6645",
-"o' c #FF6245",
-"p' c #FF6045",
-"q' c #FF6146",
-"r' c #FF6246",
-"s' c #FF6446",
-"t' c #FF6545",
-"u' c #FE763F",
-"v' c #FE7237",
-"w' c #C65331",
-"x' c #3D272A",
-"y' c #0D1E2B",
-"z' c #683032",
-"A' c #F9453A",
-"B' c #FD5341",
-"C' c #FE5A46",
-"D' c #FF5A48",
-"E' c #FE5948",
-"F' c #FD5A47",
-"G' c #FC5D43",
-"H' c #F95B3D",
-"I' c #713F37",
-"J' c #1E2D32",
-"K' c #C44531",
-"L' c #EF7A2F",
-"M' c #6B2E2C",
-"N' c #0F0E2C",
-"O' c #F56633",
-"P' c #FA803A",
-"Q' c #FC673E",
-"R' c #FD673E",
-"S' c #FC6F3C",
-"T' c #FA6E3B",
-"U' c #C6633A",
-"V' c #A06739",
-"W' c #835638",
-"X' c #381F38",
-"Y' c #713B38",
-"Z' c #7B503C",
-"`' c #FE7741",
-" ) c #FE7344",
-".) c #FE6D46",
-"+) c #FF6946",
-"@) c #FF5E46",
-"#) c #FF5D46",
-"$) c #FF5D47",
-"%) c #FF5F48",
-"&) c #FF6248",
-"*) c #FE6941",
-"=) c #FC783C",
-"-) c #C46B35",
-";) c #892730",
-">) c #111629",
-",) c #1F2630",
-"') c #AD3939",
-")) c #FC5D41",
-"!) c #FE5946",
-"~) c #FF5848",
-"{) c #FE5549",
-"]) c #FC5E42",
-"^) c #FA673B",
-"/) c #DB7033",
-"() c #392E2B",
-"_) c #311A28",
-":) c #3C2127",
-"<) c #1D1027",
-"[) c #92102C",
-"}) c #F58336",
-"|) c #FA673E",
-"1) c #FD6642",
-"2) c #FD5A41",
-"3) c #FC6D41",
-"4) c #FC6D3F",
-"5) c #FD683E",
-"6) c #F38C39",
-"7) c #CE6535",
-"8) c #612E34",
-"9) c #1D2637",
-"0) c #71513E",
-"a) c #FF6847",
-"b) c #FF5F47",
-"c) c #FF5A46",
-"d) c #FF5847",
-"e) c #FF5748",
-"f) c #FF594A",
-"g) c #FF5E4B",
-"h) c #FE654C",
-"i) c #FE694B",
-"j) c #FE6B48",
-"k) c #FC6A43",
-"l) c #F7683E",
-"m) c #EC6E39",
-" ",
-" ",
-" ",
-" ",
-" ",
-" ",
-" ",
-" ",
-" . + @ # $ % ",
-" & * = - ; > , ' ) ! ~ ",
-" { ] ^ / ( _ : < [ } | 1 2 ",
-" 3 4 5 6 7 8 9 0 a b c d e f g h i j ",
-" k l m n o p q r s t u v w x y z A B C D ",
-" E F G H I J K L M N O P Q R S T U V W X Y Z ` ",
-" ...+.@.#.$.%.&.*.=.-.;.>.,.S '.).!.~.{.].^./.(._. ",
-" :.<.[.}.|.1.2.3.4.5.6.7.8.9.0.a.b.c.d.e.!.S f.g.h.i.j.k. ",
-" l.m.n.o.p.q.r.s.t.u.J v.w.x.y.z.A.c.d.d.B.C.D.E.F.G.H.I. ",
-" J.K.L.M.N.O.P.Q.R.t S.T.U.V.W.X.Y.Z.`. +d.d..+B.'.++@+#+$+%+ ",
-" &+*+=+-+;+>+,+'+)+!+~+{+]+^+/+(+_+:+<+[+}+|+1+d.2+3+4+d.5+6+7+8+9+0+ ",
-" a+b+c+d+e+f+g+h+i+j+k+l+m+n+^+o+p+q+r+s+t+u+v+b.w+x+y+z+A+w+B+C+D+E+F+G+ ",
-" H+I+J+K+L+M+N+O+P+Q+R+S+T+U+V+W+Q ,.X+Y+Z+`+ @.@+@@@#@$@%@&@*@=@#@-@;@>@,@'@ ",
-" )@!@~@{@]@^@/@(@_@:@<@[@}@|@1@2@3@R ,.4@5@6@7@8@9@0@a@#@b@c@=@d@e@f@g@>@h@i@j@ ",
-" k@l@m@n@o@p@q@r@s@t@u@v@w@x@y@^+R S z@A@z.q+B@C@D@E@F@G@H@#@e@#@#@f@g@I@J@K@L@ ",
-" M@N@O@P@Q@R@S@T@U@V@W@X@Y@Z@`@ #.#+#+#S A@@###$#%#&#*#=#-#f@B+B+B+f@;#>#,#'#)# ",
-" !#~#{#]#^#/#(#(#_#:#<#[#}#|#1#^+.#S +#+#z@2#3#4#5#6#7#8#9#0#A.B+B+a#A.@@b#c#d# ",
-" e#f#g#h#i#j#k#l#m#n#o#p#q#r#s#t#u#v#.#w#S R ^+x#y#z#A#B#C#D#-#A.a#`.`.b.g@E#d#F# ",
-" G#0@H#I#J#K#L#M#N#O#P#Q#R#S#T#U#V#>.W#3@v#R R X+X#Y#s#Z#`# $.$+$@$g@f@5+5+#$6+$$%$&$ ",
-" *$=$-$;$>$,$'$)$!$~${$]$^$/$($_$*$u#:$Q 3@,.X+z.<$[$}$|$1$2$3$4$5$6$7$e@8$#$G@9$0$a$ ",
-" ,.4@E#b$c$d$e$f$g$h$i$j$k$l$m$n$`@>.:$o$3@,. #a.p$q$r$s$t$u$v$w$x$y$z$A$B$#@C$D$E$F$G$ ",
-" R S H$v+I$J$K$n+L$:$o$o$M$N$L$O$P$Q$R$N$o$3@S$T$U$V$W$X$Y$Z$`$ %.%+%@%#%$%%%&%*%=%-%;%>% ",
-" E.,%~.'%Z.4@v W#o$)%)%)%Q !%~%{%]%^%Q$u u#/%(%_%:%<%[%}%|%1%2%3%4%=%5%6%7%8%9%0%a%b%c%d% ",
-" e%f%g%a#,%,%z@R 3@3@3@)%Q h%i%j%k%l%m%{+n%o%p%q%r%s%t%u%v%w%x%y%z%A%*%B%C%D%E%F%G%H%I% ",
-" J%K%L%M%N%D.S v#)%)%O%P%Q%R%S%T%U%V%W%X%Y%Z%`% &.&+&@&#&$&%&&&*&f@a##@=&-&;&>&,&'&)& ",
-" !&~&{&]&^&.#w#^&/%/&(&_&:&<&[&}&|&1&2&3&4&5&6&7&8&9&0&a&b&c&d&e&e@1+5+e@f&g&h&i&j& ",
-" k&l&m&n&o&p&q&r&i%s&3.t&u&v&w&x&y&z&A&B&C&D&E&F&G&H&I&J&K&L&M&N&O&P&1+`.e@f&Q&R&S&T& ",
-" 0 U&V&W&X&<&Y&j%Z&`& *.*+*@*#*$*%*&***=*-*;*>*>*,*'*)*!*~*{*]*^*/*(*a#B+#@_*:*<*[*}* ",
-" |*1*2*3*4*5*6*7*8*9*0*a*b*c*d*e*f*g*h*i*j*k*l*m*n*o*p*q*r*s*t*u*v*E.w*d.e@x*y*z*A*B* ",
-" C*D*E*F*G*H*I*J*K*L*M*N*O*P*Q*R*S*T*U*V*W*l*X*o*o*Y*Z*`* =.=+=@=#='%$=%=e@&=*===-=;= ",
-" >=,='=)=!=~={=]=^=/=(=_=:=<=[=}=|=1=2=3=4=5=p*6=6=7=8=9=0=a=b=c=d=A@~.b.B+e=f=g=h=i= ",
-" j=k=l=m=n=o=p=q=r=s=t=u=v=w=x=y=z=A=5=Z*B=C=D=E=8=F=G=H=I=J=K=S$R z@'%L=M=N=O= ",
-" P=Q=R=S=T=U=V=W=X=Y=Z=`= -.-+-@-#-$-5=p*E=D=%-%-q*&-*-=---;->-,-/%3@^+'-)-!-~- ",
-" {-]-^-/-(-_-:-<-[-}-|-1-2- -3-4-5-6-7-8-n*m*9-0-9-o*a-b-c-d-e-f-g-(&h%w c h-i- ",
-" j-k-l-m-n-o-p-q-r-s-t-u-v-w-,-x-y-z-A-B-C-D-E-E-F-G-_@m*H-I-J-K-L-M-N-O-P-(+Q- ",
-" R-S-T-U-V-W-X-Y-Z-`- ;.;+;@;#;$;%;&;*;=;-;-;;;>;,;';);!;~;{;];^;/;(;_;:;<;[;}; ",
-" |;1;2;3;4;5;6;7;8;9;0;a;b;c;d;e;f;g;h;i;j;j;k;k;l;m;n;o;p;q;r;s;t;u;v;w;x;y; ",
-" z;A;B;C;D;E;F;G;H;I;J;K;L;M;N;O;P;Q;R;S;T;U;V;W;X;k;Y;Z;`; >r;.>+>@>#>$> ",
-" %>&>*>=>->;>>>,>'>,>)>F;8;!>~>{>]>^>/>(>_>:>i;<>[>X;}>i;|>1>q;2>3>4>5>6> ",
-" 7>8>=>9>0>a>b>c>d>,>e>e>f>g>h>i>j>k>l>m>n>:>i;o>p>q>W;r>s>t>p;u>v>w>x>y> ",
-" z>A>9>0>B>C>c>D>E>F>G>G>F>H>I>J>K>L>M>N>O>P>Q>R>o>R>T;s>S>S>S>t>1>T>U>V>W>X>Y> ",
-" Z>`> ,9>B>.,D>+,@,#,$,%,$,&,*,=,-,;,>,,,',),P>!,!,_>~,t>s>{,],{,],^,/,(,_,:,<,[,}, ",
-" |,`>1,2,3,G>+,4,o>o>4,@,@,5,6,7,8,9,0,a,b,c,d,e,f,g,h, >~,|>T;T;T;i,j,k,l,m,n,o,p,q,r, ",
-" s,t,u,v,G>%,@,o>w,R>x,p>@,5,6,y,z,A,B,C,D,E,F,G,H,I,J,K,L,L,i,i;i;i;Q>S;M,N,P>O,P,Q,R,S, ",
-" T,U,V,W,%,X,Y,Z,`,[>q>@, '.'+'@'#'$'%'&'*'='-';'>',''')'!'~'{'N,i,:>_>]'M,M,Q>_>^'/'('_':'<' ",
-" ['}'|'1'$,X,2'p>3'4'2'@,5'6'7'8'9'0'a'b'c'd'e'f'g'h'i'j'k'l'd,m'g, > >n'o'p'q'r's't'.>u'v'w'x' ",
-" y'z'A'B'C'X,X,2'D'E'E'F'G'H'I'J'K'L'M'N'O'P'Q'R'S'T'U'V'W'X'Y'Z'`' ).)+)r'@)#)$)%)&)l;1>*)=)-);) ",
-" >),)')))!)X,E'X,~){)d>!)])^)/)()_):)<)[)})|)1)f,2)3)4)5)6)7)8)9)0)*--*a)b)c)d)e)f)g)h)i)j)k)l)m) ",
-" ",
-" ",
-" ",
-" ",
-" ",
-" ",
-" ",
-" "};
diff --git a/ext/json/lib/json/pure.rb b/ext/json/lib/json/pure.rb
deleted file mode 100644
index b86d905523..0000000000
--- a/ext/json/lib/json/pure.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-require 'json/common'
-require 'json/pure/parser'
-require 'json/pure/generator'
-
-module JSON
- begin
- require 'iconv'
- # An iconv instance to convert from UTF8 to UTF16 Big Endian.
- UTF16toUTF8 = Iconv.new('utf-8', 'utf-16be') # :nodoc:
- # An iconv instance to convert from UTF16 Big Endian to UTF8.
- UTF8toUTF16 = Iconv.new('utf-16be', 'utf-8') # :nodoc:
- UTF8toUTF16.iconv('no bom')
- rescue Errno::EINVAL, Iconv::InvalidEncoding
- # Iconv doesn't support big endian utf-16. Let's try to hack this manually
- # into the converters.
- begin
- old_verbose, $VERBSOSE = $VERBOSE, nil
- # An iconv instance to convert from UTF8 to UTF16 Big Endian.
- UTF16toUTF8 = Iconv.new('utf-8', 'utf-16') # :nodoc:
- # An iconv instance to convert from UTF16 Big Endian to UTF8.
- UTF8toUTF16 = Iconv.new('utf-16', 'utf-8') # :nodoc:
- UTF8toUTF16.iconv('no bom')
- if UTF8toUTF16.iconv("\xe2\x82\xac") == "\xac\x20"
- swapper = Class.new do
- def initialize(iconv) # :nodoc:
- @iconv = iconv
- end
-
- def iconv(string) # :nodoc:
- result = @iconv.iconv(string)
- JSON.swap!(result)
- end
- end
- UTF8toUTF16 = swapper.new(UTF8toUTF16) # :nodoc:
- end
- if UTF16toUTF8.iconv("\xac\x20") == "\xe2\x82\xac"
- swapper = Class.new do
- def initialize(iconv) # :nodoc:
- @iconv = iconv
- end
-
- def iconv(string) # :nodoc:
- string = JSON.swap!(string.dup)
- @iconv.iconv(string)
- end
- end
- UTF16toUTF8 = swapper.new(UTF16toUTF8) # :nodoc:
- end
- rescue Errno::EINVAL, Iconv::InvalidEncoding
- raise MissingUnicodeSupport, "iconv doesn't seem to support UTF-8/UTF-16 conversions"
- ensure
- $VERBOSE = old_verbose
- end
- rescue LoadError
- raise MissingUnicodeSupport,
- "iconv couldn't be loaded, which is required for UTF-8/UTF-16 conversions"
- end
-
- # Swap consecutive bytes of _string_ in place.
- def self.swap!(string) # :nodoc:
- 0.upto(string.size / 2) do |i|
- break unless string[2 * i + 1]
- string[2 * i], string[2 * i + 1] = string[2 * i + 1], string[2 * i]
- end
- string
- end
-
- # This module holds all the modules/classes that implement JSON's
- # functionality in pure ruby.
- module Pure
- $DEBUG and warn "Using pure library for JSON."
- JSON.parser = Parser
- JSON.generator = Generator
- end
-end
diff --git a/ext/json/lib/json/pure/generator.rb b/ext/json/lib/json/pure/generator.rb
deleted file mode 100644
index c8bbfd09ee..0000000000
--- a/ext/json/lib/json/pure/generator.rb
+++ /dev/null
@@ -1,394 +0,0 @@
-module JSON
- MAP = {
- "\x0" => '\u0000',
- "\x1" => '\u0001',
- "\x2" => '\u0002',
- "\x3" => '\u0003',
- "\x4" => '\u0004',
- "\x5" => '\u0005',
- "\x6" => '\u0006',
- "\x7" => '\u0007',
- "\b" => '\b',
- "\t" => '\t',
- "\n" => '\n',
- "\xb" => '\u000b',
- "\f" => '\f',
- "\r" => '\r',
- "\xe" => '\u000e',
- "\xf" => '\u000f',
- "\x10" => '\u0010',
- "\x11" => '\u0011',
- "\x12" => '\u0012',
- "\x13" => '\u0013',
- "\x14" => '\u0014',
- "\x15" => '\u0015',
- "\x16" => '\u0016',
- "\x17" => '\u0017',
- "\x18" => '\u0018',
- "\x19" => '\u0019',
- "\x1a" => '\u001a',
- "\x1b" => '\u001b',
- "\x1c" => '\u001c',
- "\x1d" => '\u001d',
- "\x1e" => '\u001e',
- "\x1f" => '\u001f',
- '"' => '\"',
- '\\' => '\\\\',
- '/' => '\/',
- } # :nodoc:
-
- # Convert a UTF8 encoded Ruby string _string_ to a JSON string, encoded with
- # UTF16 big endian characters as \u????, and return it.
- def utf8_to_json(string) # :nodoc:
- string = string.gsub(/["\\\/\x0-\x1f]/) { MAP[$&] }
- string.gsub!(/(
- (?:
- [\xc2-\xdf][\x80-\xbf] |
- [\xe0-\xef][\x80-\xbf]{2} |
- [\xf0-\xf4][\x80-\xbf]{3}
- )+ |
- [\x80-\xc1\xf5-\xff] # invalid
- )/nx) { |c|
- c.size == 1 and raise GeneratorError, "invalid utf8 byte: '#{c}'"
- s = JSON::UTF8toUTF16.iconv(c).unpack('H*')[0]
- s.gsub!(/.{4}/n, '\\\\u\&')
- }
- string
- rescue Iconv::Failure => e
- raise GeneratorError, "Caught #{e.class}: #{e}"
- end
- module_function :utf8_to_json
-
- module Pure
- module Generator
- # This class is used to create State instances, that are use to hold data
- # while generating a JSON text from a a Ruby data structure.
- class State
- # Creates a State object from _opts_, which ought to be Hash to create
- # a new State instance configured by _opts_, something else to create
- # an unconfigured instance. If _opts_ is a State object, it is just
- # returned.
- def self.from_state(opts)
- case opts
- when self
- opts
- when Hash
- new(opts)
- else
- new
- end
- end
-
- # Instantiates a new State object, configured by _opts_.
- #
- # _opts_ can have the following keys:
- #
- # * *indent*: a string used to indent levels (default: ''),
- # * *space*: a string that is put after, a : or , delimiter (default: ''),
- # * *space_before*: a string that is put before a : pair delimiter (default: ''),
- # * *object_nl*: a string that is put at the end of a JSON object (default: ''),
- # * *array_nl*: a string that is put at the end of a JSON array (default: ''),
- # * *check_circular*: true if checking for circular data structures
- # should be done (the default), false otherwise.
- # * *check_circular*: true if checking for circular data structures
- # should be done, false (the default) otherwise.
- # * *allow_nan*: true if NaN, Infinity, and -Infinity should be
- # generated, otherwise an exception is thrown, if these values are
- # encountered. This options defaults to false.
- def initialize(opts = {})
- @seen = {}
- @indent = ''
- @space = ''
- @space_before = ''
- @object_nl = ''
- @array_nl = ''
- @check_circular = true
- @allow_nan = false
- configure opts
- end
-
- # This string is used to indent levels in the JSON text.
- attr_accessor :indent
-
- # This string is used to insert a space between the tokens in a JSON
- # string.
- attr_accessor :space
-
- # This string is used to insert a space before the ':' in JSON objects.
- attr_accessor :space_before
-
- # This string is put at the end of a line that holds a JSON object (or
- # Hash).
- attr_accessor :object_nl
-
- # This string is put at the end of a line that holds a JSON array.
- attr_accessor :array_nl
-
- # This integer returns the maximum level of data structure nesting in
- # the generated JSON, max_nesting = 0 if no maximum is checked.
- attr_accessor :max_nesting
-
- def check_max_nesting(depth) # :nodoc:
- return if @max_nesting.zero?
- current_nesting = depth + 1
- current_nesting > @max_nesting and
- raise NestingError, "nesting of #{current_nesting} is too deep"
- end
-
- # Returns true, if circular data structures should be checked,
- # otherwise returns false.
- def check_circular?
- @check_circular
- end
-
- # Returns true if NaN, Infinity, and -Infinity should be considered as
- # valid JSON and output.
- def allow_nan?
- @allow_nan
- end
-
- # Returns _true_, if _object_ was already seen during this generating
- # run.
- def seen?(object)
- @seen.key?(object.__id__)
- end
-
- # Remember _object_, to find out if it was already encountered (if a
- # cyclic data structure is if a cyclic data structure is rendered).
- def remember(object)
- @seen[object.__id__] = true
- end
-
- # Forget _object_ for this generating run.
- def forget(object)
- @seen.delete object.__id__
- end
-
- # Configure this State instance with the Hash _opts_, and return
- # itself.
- def configure(opts)
- @indent = opts[:indent] if opts.key?(:indent)
- @space = opts[:space] if opts.key?(:space)
- @space_before = opts[:space_before] if opts.key?(:space_before)
- @object_nl = opts[:object_nl] if opts.key?(:object_nl)
- @array_nl = opts[:array_nl] if opts.key?(:array_nl)
- @check_circular = !!opts[:check_circular] if opts.key?(:check_circular)
- @allow_nan = !!opts[:allow_nan] if opts.key?(:allow_nan)
- if !opts.key?(:max_nesting) # defaults to 19
- @max_nesting = 19
- elsif opts[:max_nesting]
- @max_nesting = opts[:max_nesting]
- else
- @max_nesting = 0
- end
- self
- end
-
- # Returns the configuration instance variables as a hash, that can be
- # passed to the configure method.
- def to_h
- result = {}
- for iv in %w[indent space space_before object_nl array_nl check_circular allow_nan max_nesting]
- result[iv.intern] = instance_variable_get("@#{iv}")
- end
- result
- end
- end
-
- module GeneratorMethods
- module Object
- # Converts this object to a string (calling #to_s), converts
- # it to a JSON string, and returns the result. This is a fallback, if no
- # special method #to_json was defined for some object.
- def to_json(*) to_s.to_json end
- end
-
- module Hash
- # Returns a JSON string containing a JSON object, that is unparsed from
- # this Hash instance.
- # _state_ is a JSON::State object, that can also be used to configure the
- # produced JSON string output further.
- # _depth_ is used to find out nesting depth, to indent accordingly.
- def to_json(state = nil, depth = 0, *)
- if state
- state = JSON.state.from_state(state)
- state.check_max_nesting(depth)
- json_check_circular(state) { json_transform(state, depth) }
- else
- json_transform(state, depth)
- end
- end
-
- private
-
- def json_check_circular(state)
- if state and state.check_circular?
- state.seen?(self) and raise JSON::CircularDatastructure,
- "circular data structures not supported!"
- state.remember self
- end
- yield
- ensure
- state and state.forget self
- end
-
- def json_shift(state, depth)
- state and not state.object_nl.empty? or return ''
- state.indent * depth
- end
-
- def json_transform(state, depth)
- delim = ','
- delim << state.object_nl if state
- result = '{'
- result << state.object_nl if state
- result << map { |key,value|
- s = json_shift(state, depth + 1)
- s << key.to_s.to_json(state, depth + 1)
- s << state.space_before if state
- s << ':'
- s << state.space if state
- s << value.to_json(state, depth + 1)
- }.join(delim)
- result << state.object_nl if state
- result << json_shift(state, depth)
- result << '}'
- result
- end
- end
-
- module Array
- # Returns a JSON string containing a JSON array, that is unparsed from
- # this Array instance.
- # _state_ is a JSON::State object, that can also be used to configure the
- # produced JSON string output further.
- # _depth_ is used to find out nesting depth, to indent accordingly.
- def to_json(state = nil, depth = 0, *)
- if state
- state = JSON.state.from_state(state)
- state.check_max_nesting(depth)
- json_check_circular(state) { json_transform(state, depth) }
- else
- json_transform(state, depth)
- end
- end
-
- private
-
- def json_check_circular(state)
- if state and state.check_circular?
- state.seen?(self) and raise JSON::CircularDatastructure,
- "circular data structures not supported!"
- state.remember self
- end
- yield
- ensure
- state and state.forget self
- end
-
- def json_shift(state, depth)
- state and not state.array_nl.empty? or return ''
- state.indent * depth
- end
-
- def json_transform(state, depth)
- delim = ','
- delim << state.array_nl if state
- result = '['
- result << state.array_nl if state
- result << map { |value|
- json_shift(state, depth + 1) << value.to_json(state, depth + 1)
- }.join(delim)
- result << state.array_nl if state
- result << json_shift(state, depth)
- result << ']'
- result
- end
- end
-
- module Integer
- # Returns a JSON string representation for this Integer number.
- def to_json(*) to_s end
- end
-
- module Float
- # Returns a JSON string representation for this Float number.
- def to_json(state = nil, *)
- case
- when infinite?
- if !state || state.allow_nan?
- to_s
- else
- raise GeneratorError, "#{self} not allowed in JSON"
- end
- when nan?
- if !state || state.allow_nan?
- to_s
- else
- raise GeneratorError, "#{self} not allowed in JSON"
- end
- else
- to_s
- end
- end
- end
-
- module String
- # This string should be encoded with UTF-8 A call to this method
- # returns a JSON string encoded with UTF16 big endian characters as
- # \u????.
- def to_json(*)
- '"' << JSON.utf8_to_json(self) << '"'
- end
-
- # Module that holds the extinding methods if, the String module is
- # included.
- module Extend
- # Raw Strings are JSON Objects (the raw bytes are stored in an array for the
- # key "raw"). The Ruby String can be created by this module method.
- def json_create(o)
- o['raw'].pack('C*')
- end
- end
-
- # Extends _modul_ with the String::Extend module.
- def self.included(modul)
- modul.extend Extend
- end
-
- # This method creates a raw object hash, that can be nested into
- # other data structures and will be unparsed as a raw string. This
- # method should be used, if you want to convert raw strings to JSON
- # instead of UTF-8 strings, e. g. binary data.
- def to_json_raw_object
- {
- JSON.create_id => self.class.name,
- 'raw' => self.unpack('C*'),
- }
- end
-
- # This method creates a JSON text from the result of
- # a call to to_json_raw_object of this String.
- def to_json_raw(*args)
- to_json_raw_object.to_json(*args)
- end
- end
-
- module TrueClass
- # Returns a JSON string for true: 'true'.
- def to_json(*) 'true' end
- end
-
- module FalseClass
- # Returns a JSON string for false: 'false'.
- def to_json(*) 'false' end
- end
-
- module NilClass
- # Returns a JSON string for nil: 'null'.
- def to_json(*) 'null' end
- end
- end
- end
- end
-end
diff --git a/ext/json/lib/json/pure/parser.rb b/ext/json/lib/json/pure/parser.rb
deleted file mode 100644
index 39bee54269..0000000000
--- a/ext/json/lib/json/pure/parser.rb
+++ /dev/null
@@ -1,260 +0,0 @@
-require 'strscan'
-
-module JSON
- module Pure
- # This class implements the JSON parser that is used to parse a JSON string
- # into a Ruby data structure.
- class Parser < StringScanner
- STRING = /" ((?:[^\x0-\x1f"\\] |
- \\["\\\/bfnrt] |
- \\u[0-9a-fA-F]{4} |
- \\[\x20-\xff])*)
- "/nx
- INTEGER = /(-?0|-?[1-9]\d*)/
- FLOAT = /(-?
- (?:0|[1-9]\d*)
- (?:
- \.\d+(?i:e[+-]?\d+) |
- \.\d+ |
- (?i:e[+-]?\d+)
- )
- )/x
- NAN = /NaN/
- INFINITY = /Infinity/
- MINUS_INFINITY = /-Infinity/
- OBJECT_OPEN = /\{/
- OBJECT_CLOSE = /\}/
- ARRAY_OPEN = /\[/
- ARRAY_CLOSE = /\]/
- PAIR_DELIMITER = /:/
- COLLECTION_DELIMITER = /,/
- TRUE = /true/
- FALSE = /false/
- NULL = /null/
- IGNORE = %r(
- (?:
- //[^\n\r]*[\n\r]| # line comments
- /\* # c-style comments
- (?:
- [^*/]| # normal chars
- /[^*]| # slashes that do not start a nested comment
- \*[^/]| # asterisks that do not end this comment
- /(?=\*/) # single slash before this comment's end
- )*
- \*/ # the End of this comment
- |[ \t\r\n]+ # whitespaces: space, horicontal tab, lf, cr
- )+
- )mx
-
- UNPARSED = Object.new
-
- # Creates a new JSON::Pure::Parser instance for the string _source_.
- #
- # It will be configured by the _opts_ hash. _opts_ can have the following
- # keys:
- # * *max_nesting*: The maximum depth of nesting allowed in the parsed data
- # structures. Disable depth checking with :max_nesting => false|nil|0,
- # it defaults to 19.
- # * *allow_nan*: If set to true, allow NaN, Infinity and -Infinity in
- # defiance of RFC 4627 to be parsed by the Parser. This option defaults
- # to false.
- # * *create_additions*: If set to false, the Parser doesn't create
- # additions even if a matchin class and create_id was found. This option
- # defaults to true.
- def initialize(source, opts = {})
- super
- if !opts.key?(:max_nesting) # defaults to 19
- @max_nesting = 19
- elsif opts[:max_nesting]
- @max_nesting = opts[:max_nesting]
- else
- @max_nesting = 0
- end
- @allow_nan = !!opts[:allow_nan]
- ca = true
- ca = opts[:create_additions] if opts.key?(:create_additions)
- @create_id = ca ? JSON.create_id : nil
- end
-
- alias source string
-
- # Parses the current JSON string _source_ and returns the complete data
- # structure as a result.
- def parse
- reset
- obj = nil
- until eos?
- case
- when scan(OBJECT_OPEN)
- obj and raise ParserError, "source '#{peek(20)}' not in JSON!"
- @current_nesting = 1
- obj = parse_object
- when scan(ARRAY_OPEN)
- obj and raise ParserError, "source '#{peek(20)}' not in JSON!"
- @current_nesting = 1
- obj = parse_array
- when skip(IGNORE)
- ;
- else
- raise ParserError, "source '#{peek(20)}' not in JSON!"
- end
- end
- obj or raise ParserError, "source did not contain any JSON!"
- obj
- end
-
- private
-
- # Unescape characters in strings.
- UNESCAPE_MAP = Hash.new { |h, k| h[k] = k.chr }
- UNESCAPE_MAP.update({
- ?" => '"',
- ?\\ => '\\',
- ?/ => '/',
- ?b => "\b",
- ?f => "\f",
- ?n => "\n",
- ?r => "\r",
- ?t => "\t",
- ?u => nil,
- })
-
- def parse_string
- if scan(STRING)
- return '' if self[1].empty?
- self[1].gsub(%r((?:\\[\\bfnrt"/]|(?:\\u(?:[A-Fa-f\d]{4}))+|\\[\x20-\xff]))n) do
- c = $&
- if u = UNESCAPE_MAP[c[1]]
- u
- else # \uXXXX
- bytes = ''
- i = 0
- while c[6 * i] == ?\\ && c[6 * i + 1] == ?u
- bytes << c[6 * i + 2, 2].to_i(16) << c[6 * i + 4, 2].to_i(16)
- i += 1
- end
- JSON::UTF16toUTF8.iconv(bytes)
- end
- end
- else
- UNPARSED
- end
- rescue Iconv::Failure => e
- raise GeneratorError, "Caught #{e.class}: #{e}"
- end
-
- def parse_value
- case
- when scan(FLOAT)
- Float(self[1])
- when scan(INTEGER)
- Integer(self[1])
- when scan(TRUE)
- true
- when scan(FALSE)
- false
- when scan(NULL)
- nil
- when (string = parse_string) != UNPARSED
- string
- when scan(ARRAY_OPEN)
- @current_nesting += 1
- ary = parse_array
- @current_nesting -= 1
- ary
- when scan(OBJECT_OPEN)
- @current_nesting += 1
- obj = parse_object
- @current_nesting -= 1
- obj
- when @allow_nan && scan(NAN)
- NaN
- when @allow_nan && scan(INFINITY)
- Infinity
- when @allow_nan && scan(MINUS_INFINITY)
- MinusInfinity
- else
- UNPARSED
- end
- end
-
- def parse_array
- raise NestingError, "nesting of #@current_nesting is to deep" if
- @max_nesting.nonzero? && @current_nesting > @max_nesting
- result = []
- delim = false
- until eos?
- case
- when (value = parse_value) != UNPARSED
- delim = false
- result << value
- skip(IGNORE)
- if scan(COLLECTION_DELIMITER)
- delim = true
- elsif match?(ARRAY_CLOSE)
- ;
- else
- raise ParserError, "expected ',' or ']' in array at '#{peek(20)}'!"
- end
- when scan(ARRAY_CLOSE)
- if delim
- raise ParserError, "expected next element in array at '#{peek(20)}'!"
- end
- break
- when skip(IGNORE)
- ;
- else
- raise ParserError, "unexpected token in array at '#{peek(20)}'!"
- end
- end
- result
- end
-
- def parse_object
- raise NestingError, "nesting of #@current_nesting is to deep" if
- @max_nesting.nonzero? && @current_nesting > @max_nesting
- result = {}
- delim = false
- until eos?
- case
- when (string = parse_string) != UNPARSED
- skip(IGNORE)
- unless scan(PAIR_DELIMITER)
- raise ParserError, "expected ':' in object at '#{peek(20)}'!"
- end
- skip(IGNORE)
- unless (value = parse_value).equal? UNPARSED
- result[string] = value
- delim = false
- skip(IGNORE)
- if scan(COLLECTION_DELIMITER)
- delim = true
- elsif match?(OBJECT_CLOSE)
- ;
- else
- raise ParserError, "expected ',' or '}' in object at '#{peek(20)}'!"
- end
- else
- raise ParserError, "expected value in object at '#{peek(20)}'!"
- end
- when scan(OBJECT_CLOSE)
- if delim
- raise ParserError, "expected next name, value pair in object at '#{peek(20)}'!"
- end
- if @create_id and klassname = result[@create_id]
- klass = JSON.deep_const_get klassname
- break unless klass and klass.json_creatable?
- result = klass.json_create(result)
- end
- break
- when skip(IGNORE)
- ;
- else
- raise ParserError, "unexpected token in object at '#{peek(20)}'!"
- end
- end
- result
- end
- end
- end
-end
diff --git a/ext/json/lib/json/version.rb b/ext/json/lib/json/version.rb
index 3d674ac44f..a69590ff9c 100644
--- a/ext/json/lib/json/version.rb
+++ b/ext/json/lib/json/version.rb
@@ -1,9 +1,5 @@
+# frozen_string_literal: true
+
module JSON
- # JSON version
- VERSION = '1.1.2'
- VERSION_ARRAY = VERSION.split(/\./).map { |x| x.to_i } # :nodoc:
- VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
- VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
- VERSION_BUILD = VERSION_ARRAY[2] # :nodoc:
- VARIANT_BINARY = false
+ VERSION = '2.19.7'
end
diff --git a/ext/json/parser/depend b/ext/json/parser/depend
new file mode 100644
index 0000000000..d4737b1dfb
--- /dev/null
+++ b/ext/json/parser/depend
@@ -0,0 +1,182 @@
+$(OBJS): $(ruby_headers)
+parser.o: parser.c $(srcdir)/../fbuffer/fbuffer.h
+
+# AUTOGENERATED DEPENDENCIES START
+parser.o: $(RUBY_EXTCONF_H)
+parser.o: $(arch_hdrdir)/ruby/config.h
+parser.o: $(hdrdir)/ruby.h
+parser.o: $(hdrdir)/ruby/assert.h
+parser.o: $(hdrdir)/ruby/backward.h
+parser.o: $(hdrdir)/ruby/backward/2/assume.h
+parser.o: $(hdrdir)/ruby/backward/2/attributes.h
+parser.o: $(hdrdir)/ruby/backward/2/bool.h
+parser.o: $(hdrdir)/ruby/backward/2/inttypes.h
+parser.o: $(hdrdir)/ruby/backward/2/limits.h
+parser.o: $(hdrdir)/ruby/backward/2/long_long.h
+parser.o: $(hdrdir)/ruby/backward/2/stdalign.h
+parser.o: $(hdrdir)/ruby/backward/2/stdarg.h
+parser.o: $(hdrdir)/ruby/defines.h
+parser.o: $(hdrdir)/ruby/encoding.h
+parser.o: $(hdrdir)/ruby/intern.h
+parser.o: $(hdrdir)/ruby/internal/abi.h
+parser.o: $(hdrdir)/ruby/internal/anyargs.h
+parser.o: $(hdrdir)/ruby/internal/arithmetic.h
+parser.o: $(hdrdir)/ruby/internal/arithmetic/char.h
+parser.o: $(hdrdir)/ruby/internal/arithmetic/double.h
+parser.o: $(hdrdir)/ruby/internal/arithmetic/fixnum.h
+parser.o: $(hdrdir)/ruby/internal/arithmetic/gid_t.h
+parser.o: $(hdrdir)/ruby/internal/arithmetic/int.h
+parser.o: $(hdrdir)/ruby/internal/arithmetic/intptr_t.h
+parser.o: $(hdrdir)/ruby/internal/arithmetic/long.h
+parser.o: $(hdrdir)/ruby/internal/arithmetic/long_long.h
+parser.o: $(hdrdir)/ruby/internal/arithmetic/mode_t.h
+parser.o: $(hdrdir)/ruby/internal/arithmetic/off_t.h
+parser.o: $(hdrdir)/ruby/internal/arithmetic/pid_t.h
+parser.o: $(hdrdir)/ruby/internal/arithmetic/short.h
+parser.o: $(hdrdir)/ruby/internal/arithmetic/size_t.h
+parser.o: $(hdrdir)/ruby/internal/arithmetic/st_data_t.h
+parser.o: $(hdrdir)/ruby/internal/arithmetic/uid_t.h
+parser.o: $(hdrdir)/ruby/internal/assume.h
+parser.o: $(hdrdir)/ruby/internal/attr/alloc_size.h
+parser.o: $(hdrdir)/ruby/internal/attr/artificial.h
+parser.o: $(hdrdir)/ruby/internal/attr/cold.h
+parser.o: $(hdrdir)/ruby/internal/attr/const.h
+parser.o: $(hdrdir)/ruby/internal/attr/constexpr.h
+parser.o: $(hdrdir)/ruby/internal/attr/deprecated.h
+parser.o: $(hdrdir)/ruby/internal/attr/diagnose_if.h
+parser.o: $(hdrdir)/ruby/internal/attr/enum_extensibility.h
+parser.o: $(hdrdir)/ruby/internal/attr/error.h
+parser.o: $(hdrdir)/ruby/internal/attr/flag_enum.h
+parser.o: $(hdrdir)/ruby/internal/attr/forceinline.h
+parser.o: $(hdrdir)/ruby/internal/attr/format.h
+parser.o: $(hdrdir)/ruby/internal/attr/maybe_unused.h
+parser.o: $(hdrdir)/ruby/internal/attr/noalias.h
+parser.o: $(hdrdir)/ruby/internal/attr/nodiscard.h
+parser.o: $(hdrdir)/ruby/internal/attr/noexcept.h
+parser.o: $(hdrdir)/ruby/internal/attr/noinline.h
+parser.o: $(hdrdir)/ruby/internal/attr/nonnull.h
+parser.o: $(hdrdir)/ruby/internal/attr/noreturn.h
+parser.o: $(hdrdir)/ruby/internal/attr/packed_struct.h
+parser.o: $(hdrdir)/ruby/internal/attr/pure.h
+parser.o: $(hdrdir)/ruby/internal/attr/restrict.h
+parser.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h
+parser.o: $(hdrdir)/ruby/internal/attr/warning.h
+parser.o: $(hdrdir)/ruby/internal/attr/weakref.h
+parser.o: $(hdrdir)/ruby/internal/cast.h
+parser.o: $(hdrdir)/ruby/internal/compiler_is.h
+parser.o: $(hdrdir)/ruby/internal/compiler_is/apple.h
+parser.o: $(hdrdir)/ruby/internal/compiler_is/clang.h
+parser.o: $(hdrdir)/ruby/internal/compiler_is/gcc.h
+parser.o: $(hdrdir)/ruby/internal/compiler_is/intel.h
+parser.o: $(hdrdir)/ruby/internal/compiler_is/msvc.h
+parser.o: $(hdrdir)/ruby/internal/compiler_is/sunpro.h
+parser.o: $(hdrdir)/ruby/internal/compiler_since.h
+parser.o: $(hdrdir)/ruby/internal/config.h
+parser.o: $(hdrdir)/ruby/internal/constant_p.h
+parser.o: $(hdrdir)/ruby/internal/core.h
+parser.o: $(hdrdir)/ruby/internal/core/rarray.h
+parser.o: $(hdrdir)/ruby/internal/core/rbasic.h
+parser.o: $(hdrdir)/ruby/internal/core/rbignum.h
+parser.o: $(hdrdir)/ruby/internal/core/rclass.h
+parser.o: $(hdrdir)/ruby/internal/core/rdata.h
+parser.o: $(hdrdir)/ruby/internal/core/rfile.h
+parser.o: $(hdrdir)/ruby/internal/core/rhash.h
+parser.o: $(hdrdir)/ruby/internal/core/robject.h
+parser.o: $(hdrdir)/ruby/internal/core/rregexp.h
+parser.o: $(hdrdir)/ruby/internal/core/rstring.h
+parser.o: $(hdrdir)/ruby/internal/core/rstruct.h
+parser.o: $(hdrdir)/ruby/internal/core/rtypeddata.h
+parser.o: $(hdrdir)/ruby/internal/ctype.h
+parser.o: $(hdrdir)/ruby/internal/dllexport.h
+parser.o: $(hdrdir)/ruby/internal/dosish.h
+parser.o: $(hdrdir)/ruby/internal/encoding/coderange.h
+parser.o: $(hdrdir)/ruby/internal/encoding/ctype.h
+parser.o: $(hdrdir)/ruby/internal/encoding/encoding.h
+parser.o: $(hdrdir)/ruby/internal/encoding/pathname.h
+parser.o: $(hdrdir)/ruby/internal/encoding/re.h
+parser.o: $(hdrdir)/ruby/internal/encoding/sprintf.h
+parser.o: $(hdrdir)/ruby/internal/encoding/string.h
+parser.o: $(hdrdir)/ruby/internal/encoding/symbol.h
+parser.o: $(hdrdir)/ruby/internal/encoding/transcode.h
+parser.o: $(hdrdir)/ruby/internal/error.h
+parser.o: $(hdrdir)/ruby/internal/eval.h
+parser.o: $(hdrdir)/ruby/internal/event.h
+parser.o: $(hdrdir)/ruby/internal/fl_type.h
+parser.o: $(hdrdir)/ruby/internal/gc.h
+parser.o: $(hdrdir)/ruby/internal/glob.h
+parser.o: $(hdrdir)/ruby/internal/globals.h
+parser.o: $(hdrdir)/ruby/internal/has/attribute.h
+parser.o: $(hdrdir)/ruby/internal/has/builtin.h
+parser.o: $(hdrdir)/ruby/internal/has/c_attribute.h
+parser.o: $(hdrdir)/ruby/internal/has/cpp_attribute.h
+parser.o: $(hdrdir)/ruby/internal/has/declspec_attribute.h
+parser.o: $(hdrdir)/ruby/internal/has/extension.h
+parser.o: $(hdrdir)/ruby/internal/has/feature.h
+parser.o: $(hdrdir)/ruby/internal/has/warning.h
+parser.o: $(hdrdir)/ruby/internal/intern/array.h
+parser.o: $(hdrdir)/ruby/internal/intern/bignum.h
+parser.o: $(hdrdir)/ruby/internal/intern/class.h
+parser.o: $(hdrdir)/ruby/internal/intern/compar.h
+parser.o: $(hdrdir)/ruby/internal/intern/complex.h
+parser.o: $(hdrdir)/ruby/internal/intern/cont.h
+parser.o: $(hdrdir)/ruby/internal/intern/dir.h
+parser.o: $(hdrdir)/ruby/internal/intern/enum.h
+parser.o: $(hdrdir)/ruby/internal/intern/enumerator.h
+parser.o: $(hdrdir)/ruby/internal/intern/error.h
+parser.o: $(hdrdir)/ruby/internal/intern/eval.h
+parser.o: $(hdrdir)/ruby/internal/intern/file.h
+parser.o: $(hdrdir)/ruby/internal/intern/hash.h
+parser.o: $(hdrdir)/ruby/internal/intern/io.h
+parser.o: $(hdrdir)/ruby/internal/intern/load.h
+parser.o: $(hdrdir)/ruby/internal/intern/marshal.h
+parser.o: $(hdrdir)/ruby/internal/intern/numeric.h
+parser.o: $(hdrdir)/ruby/internal/intern/object.h
+parser.o: $(hdrdir)/ruby/internal/intern/parse.h
+parser.o: $(hdrdir)/ruby/internal/intern/proc.h
+parser.o: $(hdrdir)/ruby/internal/intern/process.h
+parser.o: $(hdrdir)/ruby/internal/intern/random.h
+parser.o: $(hdrdir)/ruby/internal/intern/range.h
+parser.o: $(hdrdir)/ruby/internal/intern/rational.h
+parser.o: $(hdrdir)/ruby/internal/intern/re.h
+parser.o: $(hdrdir)/ruby/internal/intern/ruby.h
+parser.o: $(hdrdir)/ruby/internal/intern/select.h
+parser.o: $(hdrdir)/ruby/internal/intern/select/largesize.h
+parser.o: $(hdrdir)/ruby/internal/intern/set.h
+parser.o: $(hdrdir)/ruby/internal/intern/signal.h
+parser.o: $(hdrdir)/ruby/internal/intern/sprintf.h
+parser.o: $(hdrdir)/ruby/internal/intern/string.h
+parser.o: $(hdrdir)/ruby/internal/intern/struct.h
+parser.o: $(hdrdir)/ruby/internal/intern/thread.h
+parser.o: $(hdrdir)/ruby/internal/intern/time.h
+parser.o: $(hdrdir)/ruby/internal/intern/variable.h
+parser.o: $(hdrdir)/ruby/internal/intern/vm.h
+parser.o: $(hdrdir)/ruby/internal/interpreter.h
+parser.o: $(hdrdir)/ruby/internal/iterator.h
+parser.o: $(hdrdir)/ruby/internal/memory.h
+parser.o: $(hdrdir)/ruby/internal/method.h
+parser.o: $(hdrdir)/ruby/internal/module.h
+parser.o: $(hdrdir)/ruby/internal/newobj.h
+parser.o: $(hdrdir)/ruby/internal/scan_args.h
+parser.o: $(hdrdir)/ruby/internal/special_consts.h
+parser.o: $(hdrdir)/ruby/internal/static_assert.h
+parser.o: $(hdrdir)/ruby/internal/stdalign.h
+parser.o: $(hdrdir)/ruby/internal/stdbool.h
+parser.o: $(hdrdir)/ruby/internal/stdckdint.h
+parser.o: $(hdrdir)/ruby/internal/symbol.h
+parser.o: $(hdrdir)/ruby/internal/value.h
+parser.o: $(hdrdir)/ruby/internal/value_type.h
+parser.o: $(hdrdir)/ruby/internal/variable.h
+parser.o: $(hdrdir)/ruby/internal/warning_push.h
+parser.o: $(hdrdir)/ruby/internal/xmalloc.h
+parser.o: $(hdrdir)/ruby/missing.h
+parser.o: $(hdrdir)/ruby/onigmo.h
+parser.o: $(hdrdir)/ruby/oniguruma.h
+parser.o: $(hdrdir)/ruby/ruby.h
+parser.o: $(hdrdir)/ruby/st.h
+parser.o: $(hdrdir)/ruby/subst.h
+parser.o: $(srcdir)/../fbuffer/fbuffer.h
+parser.o: $(srcdir)/../json.h
+parser.o: $(srcdir)/../simd/simd.h
+parser.o: $(srcdir)/../vendor/ryu.h
+parser.o: parser.c
+# AUTOGENERATED DEPENDENCIES END
diff --git a/ext/json/parser/extconf.rb b/ext/json/parser/extconf.rb
new file mode 100644
index 0000000000..f12fc2dddc
--- /dev/null
+++ b/ext/json/parser/extconf.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+require 'mkmf'
+
+$defs << "-DJSON_DEBUG" if ENV.fetch("JSON_DEBUG", "0") != "0"
+have_func("rb_enc_interned_str", "ruby/encoding.h") # RUBY_VERSION >= 3.0
+have_func("rb_str_to_interned_str", "ruby.h") # RUBY_VERSION >= 3.0
+have_func("rb_hash_new_capa", "ruby.h") # RUBY_VERSION >= 3.2
+have_func("rb_hash_bulk_insert", "ruby.h") # Missing on TruffleRuby
+
+if RUBY_ENGINE == "ruby"
+ have_const("RUBY_TYPED_EMBEDDABLE", "ruby.h") # RUBY_VERSION >= 3.3
+end
+
+append_cflags("-std=c99")
+
+if enable_config('parser-use-simd', default=!ENV["JSON_DISABLE_SIMD"])
+ load __dir__ + "/../simd/conf.rb"
+end
+
+create_makefile 'json/ext/parser'
diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c
new file mode 100644
index 0000000000..503bed1fd4
--- /dev/null
+++ b/ext/json/parser/parser.c
@@ -0,0 +1,1757 @@
+#include "../json.h"
+#include "../vendor/ryu.h"
+#include "../simd/simd.h"
+
+static VALUE mJSON, eNestingError, Encoding_UTF_8;
+static VALUE CNaN, CInfinity, CMinusInfinity;
+
+static ID i_new, i_try_convert, i_uminus, i_encode;
+
+static VALUE sym_max_nesting, sym_allow_nan, sym_allow_trailing_comma, sym_allow_control_characters,
+ sym_allow_invalid_escape, sym_symbolize_names, sym_freeze, sym_decimal_class, sym_on_load,
+ sym_allow_duplicate_key;
+
+static int binary_encindex;
+static int utf8_encindex;
+
+#ifndef HAVE_RB_HASH_BULK_INSERT
+// For TruffleRuby
+static void
+rb_hash_bulk_insert(long count, const VALUE *pairs, VALUE hash)
+{
+ long index = 0;
+ while (index < count) {
+ VALUE name = pairs[index++];
+ VALUE value = pairs[index++];
+ rb_hash_aset(hash, name, value);
+ }
+ RB_GC_GUARD(hash);
+}
+#endif
+
+#ifndef HAVE_RB_HASH_NEW_CAPA
+#define rb_hash_new_capa(n) rb_hash_new()
+#endif
+
+#ifndef HAVE_RB_STR_TO_INTERNED_STR
+static VALUE rb_str_to_interned_str(VALUE str)
+{
+ return rb_funcall(rb_str_freeze(str), i_uminus, 0);
+}
+#endif
+
+/* name cache */
+
+#include <string.h>
+#include <ctype.h>
+
+// Object names are likely to be repeated, and are frozen.
+// As such we can re-use them if we keep a cache of the ones we've seen so far,
+// and save much more expensive lookups into the global fstring table.
+// This cache implementation is deliberately simple, as we're optimizing for compactness,
+// to be able to fit safely on the stack.
+// As such, binary search into a sorted array gives a good tradeoff between compactness and
+// performance.
+#define JSON_RVALUE_CACHE_CAPA 63
+typedef struct rvalue_cache_struct {
+ int length;
+ VALUE entries[JSON_RVALUE_CACHE_CAPA];
+} rvalue_cache;
+
+static rb_encoding *enc_utf8;
+
+#define JSON_RVALUE_CACHE_MAX_ENTRY_LENGTH 55
+
+static inline VALUE build_interned_string(const char *str, const long length)
+{
+# ifdef HAVE_RB_ENC_INTERNED_STR
+ return rb_enc_interned_str(str, length, enc_utf8);
+# else
+ VALUE rstring = rb_utf8_str_new(str, length);
+ return rb_funcall(rb_str_freeze(rstring), i_uminus, 0);
+# endif
+}
+
+static inline VALUE build_symbol(const char *str, const long length)
+{
+ return rb_str_intern(build_interned_string(str, length));
+}
+
+static void rvalue_cache_insert_at(rvalue_cache *cache, int index, VALUE rstring)
+{
+ MEMMOVE(&cache->entries[index + 1], &cache->entries[index], VALUE, cache->length - index);
+ cache->length++;
+ cache->entries[index] = rstring;
+}
+
+#define rstring_cache_memcmp memcmp
+
+#if JSON_CPU_LITTLE_ENDIAN_64BITS
+#if __has_builtin(__builtin_bswap64)
+#undef rstring_cache_memcmp
+ALWAYS_INLINE(static) int rstring_cache_memcmp(const char *str, const char *rptr, const long length)
+{
+ // The libc memcmp has numerous complex optimizations, but in this particular case,
+ // we know the string is small (JSON_RVALUE_CACHE_MAX_ENTRY_LENGTH), so being able to
+ // inline a simpler memcmp outperforms calling the libc version.
+ long i = 0;
+
+ for (; i + 8 <= length; i += 8) {
+ uint64_t a, b;
+ memcpy(&a, str + i, 8);
+ memcpy(&b, rptr + i, 8);
+ if (a != b) {
+ a = __builtin_bswap64(a);
+ b = __builtin_bswap64(b);
+ return (a < b) ? -1 : 1;
+ }
+ }
+
+ for (; i < length; i++) {
+ if (str[i] != rptr[i]) {
+ return (str[i] < rptr[i]) ? -1 : 1;
+ }
+ }
+
+ return 0;
+}
+#endif
+#endif
+
+ALWAYS_INLINE(static) int rstring_cache_cmp(const char *str, const long length, VALUE rstring)
+{
+ const char *rstring_ptr;
+ long rstring_length;
+
+ RSTRING_GETMEM(rstring, rstring_ptr, rstring_length);
+
+ if (length == rstring_length) {
+ return rstring_cache_memcmp(str, rstring_ptr, length);
+ } else {
+ return (int)(length - rstring_length);
+ }
+}
+
+ALWAYS_INLINE(static) VALUE rstring_cache_fetch(rvalue_cache *cache, const char *str, const long length)
+{
+ int low = 0;
+ int high = cache->length - 1;
+
+ while (low <= high) {
+ int mid = (high + low) >> 1;
+ VALUE entry = cache->entries[mid];
+ int cmp = rstring_cache_cmp(str, length, entry);
+
+ if (cmp == 0) {
+ return entry;
+ } else if (cmp > 0) {
+ low = mid + 1;
+ } else {
+ high = mid - 1;
+ }
+ }
+
+ VALUE rstring = build_interned_string(str, length);
+
+ if (cache->length < JSON_RVALUE_CACHE_CAPA) {
+ rvalue_cache_insert_at(cache, low, rstring);
+ }
+ return rstring;
+}
+
+static VALUE rsymbol_cache_fetch(rvalue_cache *cache, const char *str, const long length)
+{
+ int low = 0;
+ int high = cache->length - 1;
+
+ while (low <= high) {
+ int mid = (high + low) >> 1;
+ VALUE entry = cache->entries[mid];
+ int cmp = rstring_cache_cmp(str, length, rb_sym2str(entry));
+
+ if (cmp == 0) {
+ return entry;
+ } else if (cmp > 0) {
+ low = mid + 1;
+ } else {
+ high = mid - 1;
+ }
+ }
+
+ VALUE rsymbol = build_symbol(str, length);
+
+ if (cache->length < JSON_RVALUE_CACHE_CAPA) {
+ rvalue_cache_insert_at(cache, low, rsymbol);
+ }
+ return rsymbol;
+}
+
+/* rvalue stack */
+
+#define RVALUE_STACK_INITIAL_CAPA 128
+
+enum rvalue_stack_type {
+ RVALUE_STACK_HEAP_ALLOCATED = 0,
+ RVALUE_STACK_STACK_ALLOCATED = 1,
+};
+
+typedef struct rvalue_stack_struct {
+ enum rvalue_stack_type type;
+ long capa;
+ long head;
+ VALUE *ptr;
+} rvalue_stack;
+
+static rvalue_stack *rvalue_stack_spill(rvalue_stack *old_stack, VALUE *handle, rvalue_stack **stack_ref);
+
+static rvalue_stack *rvalue_stack_grow(rvalue_stack *stack, VALUE *handle, rvalue_stack **stack_ref)
+{
+ long required = stack->capa * 2;
+
+ if (stack->type == RVALUE_STACK_STACK_ALLOCATED) {
+ stack = rvalue_stack_spill(stack, handle, stack_ref);
+ } else {
+ REALLOC_N(stack->ptr, VALUE, required);
+ stack->capa = required;
+ }
+ return stack;
+}
+
+static VALUE rvalue_stack_push(rvalue_stack *stack, VALUE value, VALUE *handle, rvalue_stack **stack_ref)
+{
+ if (RB_UNLIKELY(stack->head >= stack->capa)) {
+ stack = rvalue_stack_grow(stack, handle, stack_ref);
+ }
+ stack->ptr[stack->head] = value;
+ stack->head++;
+ return value;
+}
+
+static inline VALUE *rvalue_stack_peek(rvalue_stack *stack, long count)
+{
+ return stack->ptr + (stack->head - count);
+}
+
+static inline void rvalue_stack_pop(rvalue_stack *stack, long count)
+{
+ stack->head -= count;
+}
+
+static void rvalue_stack_mark(void *ptr)
+{
+ rvalue_stack *stack = (rvalue_stack *)ptr;
+ long index;
+ if (stack && stack->ptr) {
+ for (index = 0; index < stack->head; index++) {
+ rb_gc_mark(stack->ptr[index]);
+ }
+ }
+}
+
+static void rvalue_stack_free_buffer(rvalue_stack *stack)
+{
+ ruby_xfree(stack->ptr);
+ stack->ptr = NULL;
+}
+
+static void rvalue_stack_free(void *ptr)
+{
+ rvalue_stack *stack = (rvalue_stack *)ptr;
+ if (stack) {
+ rvalue_stack_free_buffer(stack);
+#ifndef HAVE_RUBY_TYPED_EMBEDDABLE
+ ruby_xfree(stack);
+#endif
+ }
+}
+
+static size_t rvalue_stack_memsize(const void *ptr)
+{
+ const rvalue_stack *stack = (const rvalue_stack *)ptr;
+ return sizeof(rvalue_stack) + sizeof(VALUE) * stack->capa;
+}
+
+static const rb_data_type_t JSON_Parser_rvalue_stack_type = {
+ .wrap_struct_name = "JSON::Ext::Parser/rvalue_stack",
+ .function = {
+ .dmark = rvalue_stack_mark,
+ .dfree = rvalue_stack_free,
+ .dsize = rvalue_stack_memsize,
+ },
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_EMBEDDABLE,
+};
+
+static rvalue_stack *rvalue_stack_spill(rvalue_stack *old_stack, VALUE *handle, rvalue_stack **stack_ref)
+{
+ rvalue_stack *stack;
+ *handle = TypedData_Make_Struct(0, rvalue_stack, &JSON_Parser_rvalue_stack_type, stack);
+ *stack_ref = stack;
+ MEMCPY(stack, old_stack, rvalue_stack, 1);
+
+ stack->capa = old_stack->capa << 1;
+ stack->ptr = ALLOC_N(VALUE, stack->capa);
+ stack->type = RVALUE_STACK_HEAP_ALLOCATED;
+ MEMCPY(stack->ptr, old_stack->ptr, VALUE, old_stack->head);
+ return stack;
+}
+
+static void rvalue_stack_eagerly_release(VALUE handle)
+{
+ if (handle) {
+ rvalue_stack *stack;
+ TypedData_Get_Struct(handle, rvalue_stack, &JSON_Parser_rvalue_stack_type, stack);
+#ifdef HAVE_RUBY_TYPED_EMBEDDABLE
+ rvalue_stack_free_buffer(stack);
+#else
+ rvalue_stack_free(stack);
+ RTYPEDDATA_DATA(handle) = NULL;
+#endif
+ }
+}
+
+static int convert_UTF32_to_UTF8(char *buf, uint32_t ch)
+{
+ int len = 1;
+ if (ch <= 0x7F) {
+ buf[0] = (char) ch;
+ } else if (ch <= 0x07FF) {
+ buf[0] = (char) ((ch >> 6) | 0xC0);
+ buf[1] = (char) ((ch & 0x3F) | 0x80);
+ len++;
+ } else if (ch <= 0xFFFF) {
+ buf[0] = (char) ((ch >> 12) | 0xE0);
+ buf[1] = (char) (((ch >> 6) & 0x3F) | 0x80);
+ buf[2] = (char) ((ch & 0x3F) | 0x80);
+ len += 2;
+ } else if (ch <= 0x1fffff) {
+ buf[0] =(char) ((ch >> 18) | 0xF0);
+ buf[1] =(char) (((ch >> 12) & 0x3F) | 0x80);
+ buf[2] =(char) (((ch >> 6) & 0x3F) | 0x80);
+ buf[3] =(char) ((ch & 0x3F) | 0x80);
+ len += 3;
+ } else {
+ buf[0] = '?';
+ }
+ return len;
+}
+
+enum duplicate_key_action {
+ JSON_DEPRECATED = 0,
+ JSON_IGNORE,
+ JSON_RAISE,
+};
+
+typedef struct JSON_ParserStruct {
+ VALUE on_load_proc;
+ VALUE decimal_class;
+ ID decimal_method_id;
+ enum duplicate_key_action on_duplicate_key;
+ int max_nesting;
+ bool allow_nan;
+ bool allow_trailing_comma;
+ bool allow_control_characters;
+ bool allow_invalid_escape;
+ bool symbolize_names;
+ bool freeze;
+} JSON_ParserConfig;
+
+typedef struct JSON_ParserStateStruct {
+ VALUE *stack_handle;
+ const char *start;
+ const char *cursor;
+ const char *end;
+ rvalue_stack *stack;
+ rvalue_cache name_cache;
+ int in_array;
+ int current_nesting;
+ unsigned int emitted_deprecations;
+} JSON_ParserState;
+
+static inline size_t rest(JSON_ParserState *state) {
+ return state->end - state->cursor;
+}
+
+static inline bool eos(JSON_ParserState *state) {
+ return state->cursor >= state->end;
+}
+
+static inline char peek(JSON_ParserState *state)
+{
+ if (RB_UNLIKELY(eos(state))) {
+ return 0;
+ }
+ return *state->cursor;
+}
+
+static void cursor_position(JSON_ParserState *state, long *line_out, long *column_out)
+{
+ JSON_ASSERT(state->cursor <= state->end);
+
+ // Redundant but helpful for hardening
+ if (RB_UNLIKELY(state->cursor > state->end)) {
+ state->cursor = state->end;
+ }
+
+ const char *cursor = state->cursor;
+ long column = 0;
+ long line = 1;
+
+ while (cursor >= state->start) {
+ if (*cursor-- == '\n') {
+ break;
+ }
+ column++;
+ }
+
+ while (cursor >= state->start) {
+ if (*cursor-- == '\n') {
+ line++;
+ }
+ }
+ *line_out = line;
+ *column_out = column;
+}
+
+static void emit_parse_warning(const char *message, JSON_ParserState *state)
+{
+ long line, column;
+ cursor_position(state, &line, &column);
+
+ VALUE warning = rb_sprintf("%s at line %ld column %ld", message, line, column);
+ rb_funcall(mJSON, rb_intern("deprecation_warning"), 1, warning);
+}
+
+#define PARSE_ERROR_FRAGMENT_LEN 32
+
+static VALUE build_parse_error_message(const char *format, JSON_ParserState *state, long line, long column)
+{
+ unsigned char buffer[PARSE_ERROR_FRAGMENT_LEN + 3];
+
+ const char *ptr = "EOF";
+ if (state->cursor && state->cursor < state->end) {
+ ptr = state->cursor;
+ size_t len = 0;
+ while (len < PARSE_ERROR_FRAGMENT_LEN) {
+ char ch = ptr[len];
+ if (!ch || ch == '\n' || ch == ' ' || ch == '\t' || ch == '\r') {
+ break;
+ }
+ len++;
+ }
+
+ if (len) {
+ buffer[0] = '\'';
+ MEMCPY(buffer + 1, ptr, char, len);
+
+ while (buffer[len] >= 0x80 && buffer[len] < 0xC0) { // Is continuation byte
+ len--;
+ }
+
+ if (buffer[len] >= 0xC0) { // multibyte character start
+ len--;
+ }
+
+ buffer[len + 1] = '\'';
+ buffer[len + 2] = '\0';
+ ptr = (const char *)buffer;
+ }
+ }
+
+ VALUE message = rb_enc_sprintf(enc_utf8, format, ptr);
+ rb_str_catf(message, " at line %ld column %ld", line, column);
+ return message;
+}
+
+static VALUE parse_error_new(VALUE message, long line, long column)
+{
+ VALUE exc = rb_exc_new_str(rb_path2class("JSON::ParserError"), message);
+ rb_ivar_set(exc, rb_intern("@line"), LONG2NUM(line));
+ rb_ivar_set(exc, rb_intern("@column"), LONG2NUM(column));
+ return exc;
+}
+
+NORETURN(static) void raise_parse_error(const char *format, JSON_ParserState *state)
+{
+ long line, column;
+ cursor_position(state, &line, &column);
+ VALUE message = build_parse_error_message(format, state, line, column);
+ rb_exc_raise(parse_error_new(message, line, column));
+}
+
+NORETURN(static) void raise_parse_error_at(const char *format, JSON_ParserState *state, const char *at)
+{
+ state->cursor = at;
+ raise_parse_error(format, state);
+}
+
+/* unicode */
+
+static const signed char digit_values[256] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1,
+ -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1
+};
+
+static uint32_t unescape_unicode(JSON_ParserState *state, const char *sp, const char *spe)
+{
+ if (RB_UNLIKELY(sp > spe - 4)) {
+ raise_parse_error_at("incomplete unicode character escape sequence at %s", state, sp - 2);
+ }
+
+ const unsigned char *p = (const unsigned char *)sp;
+
+ const signed char b0 = digit_values[p[0]];
+ const signed char b1 = digit_values[p[1]];
+ const signed char b2 = digit_values[p[2]];
+ const signed char b3 = digit_values[p[3]];
+
+ if (RB_UNLIKELY((signed char)(b0 | b1 | b2 | b3) < 0)) {
+ raise_parse_error_at("incomplete unicode character escape sequence at %s", state, sp - 2);
+ }
+
+ return ((uint32_t)b0 << 12) | ((uint32_t)b1 << 8) | ((uint32_t)b2 << 4) | (uint32_t)b3;
+}
+
+#define GET_PARSER_CONFIG \
+ JSON_ParserConfig *config; \
+ TypedData_Get_Struct(self, JSON_ParserConfig, &JSON_ParserConfig_type, config)
+
+static const rb_data_type_t JSON_ParserConfig_type;
+
+static void
+json_eat_comments(JSON_ParserState *state)
+{
+ const char *start = state->cursor;
+ state->cursor++;
+
+ switch (peek(state)) {
+ case '/': {
+ state->cursor = memchr(state->cursor, '\n', state->end - state->cursor);
+ if (!state->cursor) {
+ state->cursor = state->end;
+ } else {
+ state->cursor++;
+ }
+ break;
+ }
+ case '*': {
+ state->cursor++;
+
+ while (true) {
+ const char *next_match = memchr(state->cursor, '*', state->end - state->cursor);
+ if (!next_match) {
+ raise_parse_error_at("unterminated comment, expected closing '*/'", state, start);
+ }
+
+ state->cursor = next_match + 1;
+ if (peek(state) == '/') {
+ state->cursor++;
+ break;
+ }
+ }
+ break;
+ }
+ default:
+ raise_parse_error_at("unexpected token %s", state, start);
+ break;
+ }
+}
+
+ALWAYS_INLINE(static) void
+json_eat_whitespace(JSON_ParserState *state)
+{
+ while (true) {
+ switch (peek(state)) {
+ case ' ':
+ state->cursor++;
+ break;
+ case '\n':
+ state->cursor++;
+
+ // Heuristic: if we see a newline, there is likely consecutive spaces after it.
+#if JSON_CPU_LITTLE_ENDIAN_64BITS
+ while (rest(state) > 8) {
+ uint64_t chunk;
+ memcpy(&chunk, state->cursor, sizeof(uint64_t));
+ if (chunk == 0x2020202020202020) {
+ state->cursor += 8;
+ continue;
+ }
+
+ uint32_t consecutive_spaces = trailing_zeros64(chunk ^ 0x2020202020202020) / CHAR_BIT;
+ state->cursor += consecutive_spaces;
+ break;
+ }
+#endif
+ break;
+ case '\t':
+ case '\r':
+ state->cursor++;
+ break;
+ case '/':
+ json_eat_comments(state);
+ break;
+
+ default:
+ return;
+ }
+ }
+}
+
+static inline VALUE build_string(const char *start, const char *end, bool intern, bool symbolize)
+{
+ if (symbolize) {
+ intern = true;
+ }
+ VALUE result;
+# ifdef HAVE_RB_ENC_INTERNED_STR
+ if (intern) {
+ result = rb_enc_interned_str(start, (long)(end - start), enc_utf8);
+ } else {
+ result = rb_utf8_str_new(start, (long)(end - start));
+ }
+# else
+ result = rb_utf8_str_new(start, (long)(end - start));
+ if (intern) {
+ result = rb_funcall(rb_str_freeze(result), i_uminus, 0);
+ }
+# endif
+
+ if (symbolize) {
+ result = rb_str_intern(result);
+ }
+
+ return result;
+}
+
+static inline bool json_string_cacheable_p(const char *string, size_t length)
+{
+ // We mostly want to cache strings that are likely to be repeated.
+ // Simple heuristics:
+ // - Common names aren't likely to be very long. So we just don't cache names above an arbitrary threshold.
+ // - If the first character isn't a letter, we're much less likely to see this string again.
+ return length <= JSON_RVALUE_CACHE_MAX_ENTRY_LENGTH && rb_isalpha(string[0]);
+}
+
+static inline VALUE json_string_fastpath(JSON_ParserState *state, JSON_ParserConfig *config, const char *string, const char *stringEnd, bool is_name)
+{
+ bool intern = is_name || config->freeze;
+ bool symbolize = is_name && config->symbolize_names;
+ size_t bufferSize = stringEnd - string;
+
+ if (is_name && state->in_array && RB_LIKELY(json_string_cacheable_p(string, bufferSize))) {
+ VALUE cached_key;
+ if (RB_UNLIKELY(symbolize)) {
+ cached_key = rsymbol_cache_fetch(&state->name_cache, string, bufferSize);
+ } else {
+ cached_key = rstring_cache_fetch(&state->name_cache, string, bufferSize);
+ }
+
+ if (RB_LIKELY(cached_key)) {
+ return cached_key;
+ }
+ }
+
+ return build_string(string, stringEnd, intern, symbolize);
+}
+
+#define JSON_MAX_UNESCAPE_POSITIONS 16
+typedef struct _json_unescape_positions {
+ long size;
+ const char **positions;
+ unsigned long additional_backslashes;
+} JSON_UnescapePositions;
+
+static inline const char *json_next_backslash(const char *pe, const char *stringEnd, JSON_UnescapePositions *positions)
+{
+ while (positions->size) {
+ positions->size--;
+ const char *next_position = positions->positions[0];
+ positions->positions++;
+ if (next_position >= pe) {
+ return next_position;
+ }
+ }
+
+ if (positions->additional_backslashes) {
+ positions->additional_backslashes--;
+ return memchr(pe, '\\', stringEnd - pe);
+ }
+
+ return NULL;
+}
+
+NOINLINE(static) VALUE json_string_unescape(JSON_ParserState *state, JSON_ParserConfig *config, const char *string, const char *stringEnd, bool is_name, JSON_UnescapePositions *positions)
+{
+ bool intern = is_name || config->freeze;
+ bool symbolize = is_name && config->symbolize_names;
+ size_t bufferSize = stringEnd - string;
+ const char *p = string, *pe = string, *bufferStart;
+ char *buffer;
+
+ VALUE result = rb_str_buf_new(bufferSize);
+ rb_enc_associate_index(result, utf8_encindex);
+ buffer = RSTRING_PTR(result);
+ bufferStart = buffer;
+
+#define APPEND_CHAR(chr) *buffer++ = chr; p = ++pe;
+
+ while (pe < stringEnd && (pe = json_next_backslash(pe, stringEnd, positions))) {
+ if (pe > p) {
+ MEMCPY(buffer, p, char, pe - p);
+ buffer += pe - p;
+ }
+ switch (*++pe) {
+ case '"':
+ case '/':
+ p = pe; // nothing to unescape just need to skip the backslash
+ break;
+ case '\\':
+ APPEND_CHAR('\\');
+ break;
+ case 'n':
+ APPEND_CHAR('\n');
+ break;
+ case 'r':
+ APPEND_CHAR('\r');
+ break;
+ case 't':
+ APPEND_CHAR('\t');
+ break;
+ case 'b':
+ APPEND_CHAR('\b');
+ break;
+ case 'f':
+ APPEND_CHAR('\f');
+ break;
+ case 'u': {
+ uint32_t ch = unescape_unicode(state, ++pe, stringEnd);
+ pe += 3;
+ /* To handle values above U+FFFF, we take a sequence of
+ * \uXXXX escapes in the U+D800..U+DBFF then
+ * U+DC00..U+DFFF ranges, take the low 10 bits from each
+ * to make a 20-bit number, then add 0x10000 to get the
+ * final codepoint.
+ *
+ * See Unicode 15: 3.8 "Surrogates", 5.3 "Handling
+ * Surrogate Pairs in UTF-16", and 23.6 "Surrogates
+ * Area".
+ */
+ if ((ch & 0xFC00) == 0xD800) {
+ pe++;
+ if (RB_LIKELY((pe <= stringEnd - 6) && memcmp(pe, "\\u", 2) == 0)) {
+ uint32_t sur = unescape_unicode(state, pe + 2, stringEnd);
+
+ if (RB_UNLIKELY((sur & 0xFC00) != 0xDC00)) {
+ raise_parse_error_at("invalid surrogate pair at %s", state, p);
+ }
+
+ ch = (((ch & 0x3F) << 10) | ((((ch >> 6) & 0xF) + 1) << 16) | (sur & 0x3FF));
+ pe += 5;
+ } else {
+ raise_parse_error_at("incomplete surrogate pair at %s", state, p);
+ break;
+ }
+ }
+
+ int unescape_len = convert_UTF32_to_UTF8(buffer, ch);
+ buffer += unescape_len;
+ p = ++pe;
+ break;
+ }
+ default:
+ if ((unsigned char)*pe < 0x20) {
+ if (!config->allow_control_characters) {
+ if (*pe == '\n') {
+ raise_parse_error_at("Invalid unescaped newline character (\\n) in string: %s", state, pe - 1);
+ }
+ raise_parse_error_at("invalid ASCII control character in string: %s", state, pe - 1);
+ }
+ }
+
+ if (config->allow_invalid_escape) {
+ APPEND_CHAR(*pe);
+ } else {
+ raise_parse_error_at("invalid escape character in string: %s", state, pe - 1);
+ }
+ break;
+ }
+ }
+#undef APPEND_CHAR
+
+ if (stringEnd > p) {
+ MEMCPY(buffer, p, char, stringEnd - p);
+ buffer += stringEnd - p;
+ }
+ rb_str_set_len(result, buffer - bufferStart);
+
+ if (symbolize) {
+ result = rb_str_intern(result);
+ } else if (intern) {
+ result = rb_str_to_interned_str(result);
+ }
+
+ return result;
+}
+
+#define MAX_FAST_INTEGER_SIZE 18
+#define MAX_NUMBER_STACK_BUFFER 128
+
+typedef VALUE (*json_number_decode_func_t)(const char *ptr);
+
+static inline VALUE json_decode_large_number(const char *start, long len, json_number_decode_func_t func)
+{
+ if (RB_LIKELY(len < MAX_NUMBER_STACK_BUFFER)) {
+ char buffer[MAX_NUMBER_STACK_BUFFER];
+ MEMCPY(buffer, start, char, len);
+ buffer[len] = '\0';
+ return func(buffer);
+ } else {
+ VALUE buffer_v = rb_str_tmp_new(len);
+ char *buffer = RSTRING_PTR(buffer_v);
+ MEMCPY(buffer, start, char, len);
+ buffer[len] = '\0';
+ VALUE number = func(buffer);
+ RB_GC_GUARD(buffer_v);
+ return number;
+ }
+}
+
+static VALUE json_decode_inum(const char *buffer)
+{
+ return rb_cstr2inum(buffer, 10);
+}
+
+NOINLINE(static) VALUE json_decode_large_integer(const char *start, long len)
+{
+ return json_decode_large_number(start, len, json_decode_inum);
+}
+
+static inline VALUE json_decode_integer(uint64_t mantissa, int mantissa_digits, bool negative, const char *start, const char *end)
+{
+ if (RB_LIKELY(mantissa_digits < MAX_FAST_INTEGER_SIZE)) {
+ if (negative) {
+ return INT64T2NUM(-((int64_t)mantissa));
+ }
+ return UINT64T2NUM(mantissa);
+ }
+
+ return json_decode_large_integer(start, end - start);
+}
+
+static VALUE json_decode_dnum(const char *buffer)
+{
+ return DBL2NUM(rb_cstr_to_dbl(buffer, 1));
+}
+
+NOINLINE(static) VALUE json_decode_large_float(const char *start, long len)
+{
+ return json_decode_large_number(start, len, json_decode_dnum);
+}
+
+/* Ruby JSON optimized float decoder using vendored Ryu algorithm
+ * Accepts pre-extracted mantissa and exponent from first-pass validation
+ */
+static inline VALUE json_decode_float(JSON_ParserConfig *config, uint64_t mantissa, int mantissa_digits, int64_t exponent, bool negative,
+ const char *start, const char *end)
+{
+ if (RB_UNLIKELY(config->decimal_class)) {
+ VALUE text = rb_str_new(start, end - start);
+ return rb_funcallv(config->decimal_class, config->decimal_method_id, 1, &text);
+ }
+
+ if (RB_UNLIKELY(exponent > INT32_MAX)) {
+ return negative ? CMinusInfinity : CInfinity;
+ }
+
+ if (RB_UNLIKELY(exponent < INT32_MIN)) {
+ return rb_float_new(negative ? -0.0 : 0.0);
+ }
+
+ // Fall back to rb_cstr_to_dbl for potential subnormals (rare edge case)
+ // Ryu has rounding issues with subnormals around 1e-310 (< 2.225e-308)
+ if (RB_UNLIKELY(mantissa_digits > 17 || mantissa_digits + exponent < -307)) {
+ return json_decode_large_float(start, end - start);
+ }
+
+ return DBL2NUM(ryu_s2d_from_parts(mantissa, mantissa_digits, (int32_t)exponent, negative));
+}
+
+static inline VALUE json_decode_array(JSON_ParserState *state, JSON_ParserConfig *config, long count)
+{
+ VALUE array = rb_ary_new_from_values(count, rvalue_stack_peek(state->stack, count));
+ rvalue_stack_pop(state->stack, count);
+
+ if (config->freeze) {
+ RB_OBJ_FREEZE(array);
+ }
+
+ return array;
+}
+
+static VALUE json_find_duplicated_key(size_t count, const VALUE *pairs)
+{
+ VALUE set = rb_hash_new_capa(count / 2);
+ for (size_t index = 0; index < count; index += 2) {
+ size_t before = RHASH_SIZE(set);
+ VALUE key = pairs[index];
+ rb_hash_aset(set, key, Qtrue);
+ if (RHASH_SIZE(set) == before) {
+ if (RB_SYMBOL_P(key)) {
+ return rb_sym2str(key);
+ }
+ return key;
+ }
+ }
+ return Qfalse;
+}
+
+NOINLINE(static) void emit_duplicate_key_warning(JSON_ParserState *state, VALUE duplicate_key)
+{
+ VALUE message = rb_sprintf(
+ "detected duplicate key %"PRIsVALUE" in JSON object. This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`",
+ rb_inspect(duplicate_key)
+ );
+
+ emit_parse_warning(RSTRING_PTR(message), state);
+ RB_GC_GUARD(message);
+}
+
+NORETURN(static) void raise_duplicate_key_error(JSON_ParserState *state, VALUE duplicate_key)
+{
+ VALUE message = rb_sprintf(
+ "duplicate key %"PRIsVALUE,
+ rb_inspect(duplicate_key)
+ );
+
+ long line, column;
+ cursor_position(state, &line, &column);
+ rb_str_concat(message, build_parse_error_message("", state, line, column)) ;
+ rb_exc_raise(parse_error_new(message, line, column));
+}
+
+static inline VALUE json_decode_object(JSON_ParserState *state, JSON_ParserConfig *config, size_t count)
+{
+ size_t entries_count = count / 2;
+ VALUE object = rb_hash_new_capa(entries_count);
+ const VALUE *pairs = rvalue_stack_peek(state->stack, count);
+ rb_hash_bulk_insert(count, pairs, object);
+
+ if (RB_UNLIKELY(RHASH_SIZE(object) < entries_count)) {
+ switch (config->on_duplicate_key) {
+ case JSON_IGNORE:
+ break;
+ case JSON_DEPRECATED:
+ // Only emit the first few deprecations to avoid spamming.
+ if (state->emitted_deprecations < 5) {
+ emit_duplicate_key_warning(state, json_find_duplicated_key(count, pairs));
+ state->emitted_deprecations++;
+ }
+
+ break;
+ case JSON_RAISE:
+ raise_duplicate_key_error(state, json_find_duplicated_key(count, pairs));
+ break;
+ }
+ }
+
+ rvalue_stack_pop(state->stack, count);
+
+ if (config->freeze) {
+ RB_OBJ_FREEZE(object);
+ }
+
+ return object;
+}
+
+static inline VALUE json_push_value(JSON_ParserState *state, JSON_ParserConfig *config, VALUE value)
+{
+ if (RB_UNLIKELY(config->on_load_proc)) {
+ value = rb_proc_call_with_block(config->on_load_proc, 1, &value, Qnil);
+ }
+ rvalue_stack_push(state->stack, value, state->stack_handle, &state->stack);
+ return value;
+}
+
+static const bool string_scan_table[256] = {
+ // ASCII Control Characters
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ // ASCII Characters
+ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // '"'
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, // '\\'
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+#ifdef HAVE_SIMD
+static SIMD_Implementation simd_impl = SIMD_NONE;
+#endif /* HAVE_SIMD */
+
+ALWAYS_INLINE(static) bool string_scan(JSON_ParserState *state)
+{
+#ifdef HAVE_SIMD
+#if defined(HAVE_SIMD_NEON)
+
+ uint64_t mask = 0;
+ if (string_scan_simd_neon(&state->cursor, state->end, &mask)) {
+ state->cursor += trailing_zeros64(mask) >> 2;
+ return true;
+ }
+
+#elif defined(HAVE_SIMD_SSE2)
+ if (simd_impl == SIMD_SSE2) {
+ int mask = 0;
+ if (string_scan_simd_sse2(&state->cursor, state->end, &mask)) {
+ state->cursor += trailing_zeros(mask);
+ return true;
+ }
+ }
+#endif /* HAVE_SIMD_NEON or HAVE_SIMD_SSE2 */
+#endif /* HAVE_SIMD */
+
+ while (!eos(state)) {
+ if (RB_UNLIKELY(string_scan_table[(unsigned char)*state->cursor])) {
+ return true;
+ }
+ state->cursor++;
+ }
+
+ // If the string ended with an unterminated escape sequence, we might
+ // have gone past the end.
+ if (RB_UNLIKELY(state->cursor > state->end)) {
+ state->cursor = state->end;
+ }
+
+ return false;
+}
+
+static VALUE json_parse_escaped_string(JSON_ParserState *state, JSON_ParserConfig *config, bool is_name, const char *start)
+{
+ const char *backslashes[JSON_MAX_UNESCAPE_POSITIONS];
+ JSON_UnescapePositions positions = {
+ .size = 0,
+ .positions = backslashes,
+ .additional_backslashes = 0,
+ };
+
+ do {
+ switch (*state->cursor) {
+ case '"': {
+ VALUE string = json_string_unescape(state, config, start, state->cursor, is_name, &positions);
+ state->cursor++;
+ return json_push_value(state, config, string);
+ }
+ case '\\': {
+ if (RB_LIKELY(positions.size < JSON_MAX_UNESCAPE_POSITIONS)) {
+ backslashes[positions.size] = state->cursor;
+ positions.size++;
+ } else {
+ positions.additional_backslashes++;
+ }
+ state->cursor++;
+ break;
+ }
+ default:
+ if (!config->allow_control_characters) {
+ raise_parse_error("invalid ASCII control character in string: %s", state);
+ }
+ break;
+ }
+
+ state->cursor++;
+ } while (string_scan(state));
+
+ raise_parse_error("unexpected end of input, expected closing \"", state);
+ return Qfalse;
+}
+
+ALWAYS_INLINE(static) VALUE json_parse_string(JSON_ParserState *state, JSON_ParserConfig *config, bool is_name)
+{
+ state->cursor++;
+ const char *start = state->cursor;
+
+ if (RB_UNLIKELY(!string_scan(state))) {
+ raise_parse_error("unexpected end of input, expected closing \"", state);
+ }
+
+ if (RB_LIKELY(*state->cursor == '"')) {
+ VALUE string = json_string_fastpath(state, config, start, state->cursor, is_name);
+ state->cursor++;
+ return json_push_value(state, config, string);
+ }
+ return json_parse_escaped_string(state, config, is_name, start);
+}
+
+#if JSON_CPU_LITTLE_ENDIAN_64BITS
+// From: https://lemire.me/blog/2022/01/21/swar-explained-parsing-eight-digits/
+// Additional References:
+// https://johnnylee-sde.github.io/Fast-numeric-string-to-int/
+// http://0x80.pl/notesen/2014-10-12-parsing-decimal-numbers-part-1-swar.html
+static inline uint64_t decode_8digits_unrolled(uint64_t val) {
+ const uint64_t mask = 0x000000FF000000FF;
+ const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32)
+ const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32)
+ val -= 0x3030303030303030;
+ val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8;
+ val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32;
+ return val;
+}
+
+static inline uint64_t decode_4digits_unrolled(uint32_t val) {
+ const uint32_t mask = 0x000000FF;
+ const uint32_t mul1 = 100;
+ val -= 0x30303030;
+ val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8;
+ val = ((val & mask) * mul1) + (((val >> 16) & mask));
+ return val;
+}
+#endif
+
+static inline int json_parse_digits(JSON_ParserState *state, uint64_t *accumulator)
+{
+ const char *start = state->cursor;
+
+#if JSON_CPU_LITTLE_ENDIAN_64BITS
+ while (rest(state) >= sizeof(uint64_t)) {
+ uint64_t next_8bytes;
+ memcpy(&next_8bytes, state->cursor, sizeof(uint64_t));
+
+ // From: https://github.com/simdjson/simdjson/blob/32b301893c13d058095a07d9868edaaa42ee07aa/include/simdjson/generic/numberparsing.h#L333
+ // Branchless version of: http://0x80.pl/articles/swar-digits-validate.html
+ uint64_t match = (next_8bytes & 0xF0F0F0F0F0F0F0F0) | (((next_8bytes + 0x0606060606060606) & 0xF0F0F0F0F0F0F0F0) >> 4);
+
+ if (match == 0x3333333333333333) { // 8 consecutive digits
+ *accumulator = (*accumulator * 100000000) + decode_8digits_unrolled(next_8bytes);
+ state->cursor += 8;
+ continue;
+ }
+
+ uint32_t consecutive_digits = trailing_zeros64(match ^ 0x3333333333333333) / CHAR_BIT;
+
+ if (consecutive_digits >= 4) {
+ *accumulator = (*accumulator * 10000) + decode_4digits_unrolled((uint32_t)next_8bytes);
+ state->cursor += 4;
+ consecutive_digits -= 4;
+ }
+
+ while (consecutive_digits) {
+ *accumulator = *accumulator * 10 + (*state->cursor - '0');
+ consecutive_digits--;
+ state->cursor++;
+ }
+
+ return (int)(state->cursor - start);
+ }
+#endif
+
+ char next_char;
+ while (rb_isdigit(next_char = peek(state))) {
+ *accumulator = *accumulator * 10 + (next_char - '0');
+ state->cursor++;
+ }
+ return (int)(state->cursor - start);
+}
+
+static inline VALUE json_parse_number(JSON_ParserState *state, JSON_ParserConfig *config, bool negative, const char *start)
+{
+ bool integer = true;
+ const char first_digit = *state->cursor;
+
+ // Variables for Ryu optimization - extract digits during parsing
+ int64_t exponent = 0;
+ int decimal_point_pos = -1;
+ uint64_t mantissa = 0;
+
+ // Parse integer part and extract mantissa digits
+ int mantissa_digits = json_parse_digits(state, &mantissa);
+
+ if (RB_UNLIKELY((first_digit == '0' && mantissa_digits > 1) || (negative && mantissa_digits == 0))) {
+ raise_parse_error_at("invalid number: %s", state, start);
+ }
+
+ // Parse fractional part
+ if (peek(state) == '.') {
+ integer = false;
+ decimal_point_pos = mantissa_digits; // Remember position of decimal point
+ state->cursor++;
+
+ int fractional_digits = json_parse_digits(state, &mantissa);
+ mantissa_digits += fractional_digits;
+
+ if (RB_UNLIKELY(!fractional_digits)) {
+ raise_parse_error_at("invalid number: %s", state, start);
+ }
+ }
+
+ // Parse exponent
+ if (rb_tolower(peek(state)) == 'e') {
+ integer = false;
+ state->cursor++;
+
+ bool negative_exponent = false;
+ const char next_char = peek(state);
+ if (next_char == '-' || next_char == '+') {
+ negative_exponent = next_char == '-';
+ state->cursor++;
+ }
+
+ uint64_t abs_exponent = 0;
+ int exponent_digits = json_parse_digits(state, &abs_exponent);
+
+ if (RB_UNLIKELY(!exponent_digits)) {
+ raise_parse_error_at("invalid number: %s", state, start);
+ }
+
+ if (RB_UNLIKELY(exponent_digits >= 20 || abs_exponent > (uint64_t)INT64_MAX)) {
+ exponent = negative_exponent ? INT64_MIN : INT64_MAX;
+ } else {
+ exponent = negative_exponent ? -(int64_t)abs_exponent : (int64_t)abs_exponent;
+ }
+ }
+
+ if (integer) {
+ return json_decode_integer(mantissa, mantissa_digits, negative, start, state->cursor);
+ }
+
+ // Adjust exponent based on decimal point position
+ if (decimal_point_pos >= 0) {
+ exponent -= (mantissa_digits - decimal_point_pos);
+ }
+
+ return json_decode_float(config, mantissa, mantissa_digits, exponent, negative, start, state->cursor);
+}
+
+static inline VALUE json_parse_positive_number(JSON_ParserState *state, JSON_ParserConfig *config)
+{
+ return json_parse_number(state, config, false, state->cursor);
+}
+
+static inline VALUE json_parse_negative_number(JSON_ParserState *state, JSON_ParserConfig *config)
+{
+ const char *start = state->cursor;
+ state->cursor++;
+ return json_parse_number(state, config, true, start);
+}
+
+static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config)
+{
+ json_eat_whitespace(state);
+
+ switch (peek(state)) {
+ case 'n':
+ if (rest(state) >= 4 && (memcmp(state->cursor, "null", 4) == 0)) {
+ state->cursor += 4;
+ return json_push_value(state, config, Qnil);
+ }
+
+ raise_parse_error("unexpected token %s", state);
+ break;
+ case 't':
+ if (rest(state) >= 4 && (memcmp(state->cursor, "true", 4) == 0)) {
+ state->cursor += 4;
+ return json_push_value(state, config, Qtrue);
+ }
+
+ raise_parse_error("unexpected token %s", state);
+ break;
+ case 'f':
+ // Note: memcmp with a small power of two compile to an integer comparison
+ if (rest(state) >= 5 && (memcmp(state->cursor + 1, "alse", 4) == 0)) {
+ state->cursor += 5;
+ return json_push_value(state, config, Qfalse);
+ }
+
+ raise_parse_error("unexpected token %s", state);
+ break;
+ case 'N':
+ // Note: memcmp with a small power of two compile to an integer comparison
+ if (config->allow_nan && rest(state) >= 3 && (memcmp(state->cursor + 1, "aN", 2) == 0)) {
+ state->cursor += 3;
+ return json_push_value(state, config, CNaN);
+ }
+
+ raise_parse_error("unexpected token %s", state);
+ break;
+ case 'I':
+ if (config->allow_nan && rest(state) >= 8 && (memcmp(state->cursor, "Infinity", 8) == 0)) {
+ state->cursor += 8;
+ return json_push_value(state, config, CInfinity);
+ }
+
+ raise_parse_error("unexpected token %s", state);
+ break;
+ case '-': {
+ // Note: memcmp with a small power of two compile to an integer comparison
+ if (rest(state) >= 9 && (memcmp(state->cursor + 1, "Infinity", 8) == 0)) {
+ if (config->allow_nan) {
+ state->cursor += 9;
+ return json_push_value(state, config, CMinusInfinity);
+ } else {
+ raise_parse_error("unexpected token %s", state);
+ }
+ }
+ return json_push_value(state, config, json_parse_negative_number(state, config));
+ break;
+ }
+ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
+ return json_push_value(state, config, json_parse_positive_number(state, config));
+ break;
+ case '"': {
+ // %r{\A"[^"\\\t\n\x00]*(?:\\[bfnrtu\\/"][^"\\]*)*"}
+ return json_parse_string(state, config, false);
+ break;
+ }
+ case '[': {
+ state->cursor++;
+ json_eat_whitespace(state);
+ long stack_head = state->stack->head;
+
+ if (peek(state) == ']') {
+ state->cursor++;
+ return json_push_value(state, config, json_decode_array(state, config, 0));
+ } else {
+ state->current_nesting++;
+ if (RB_UNLIKELY(config->max_nesting && (config->max_nesting < state->current_nesting))) {
+ rb_raise(eNestingError, "nesting of %d is too deep", state->current_nesting);
+ }
+ state->in_array++;
+ json_parse_any(state, config);
+ }
+
+ while (true) {
+ json_eat_whitespace(state);
+
+ const char next_char = peek(state);
+
+ if (RB_LIKELY(next_char == ',')) {
+ state->cursor++;
+ if (config->allow_trailing_comma) {
+ json_eat_whitespace(state);
+ if (peek(state) == ']') {
+ continue;
+ }
+ }
+ json_parse_any(state, config);
+ continue;
+ }
+
+ if (next_char == ']') {
+ state->cursor++;
+ long count = state->stack->head - stack_head;
+ state->current_nesting--;
+ state->in_array--;
+ return json_push_value(state, config, json_decode_array(state, config, count));
+ }
+
+ raise_parse_error("expected ',' or ']' after array value", state);
+ }
+ break;
+ }
+ case '{': {
+ const char *object_start_cursor = state->cursor;
+
+ state->cursor++;
+ json_eat_whitespace(state);
+ long stack_head = state->stack->head;
+
+ if (peek(state) == '}') {
+ state->cursor++;
+ return json_push_value(state, config, json_decode_object(state, config, 0));
+ } else {
+ state->current_nesting++;
+ if (RB_UNLIKELY(config->max_nesting && (config->max_nesting < state->current_nesting))) {
+ rb_raise(eNestingError, "nesting of %d is too deep", state->current_nesting);
+ }
+
+ if (peek(state) != '"') {
+ raise_parse_error("expected object key, got %s", state);
+ }
+ json_parse_string(state, config, true);
+
+ json_eat_whitespace(state);
+ if (peek(state) != ':') {
+ raise_parse_error("expected ':' after object key", state);
+ }
+ state->cursor++;
+
+ json_parse_any(state, config);
+ }
+
+ while (true) {
+ json_eat_whitespace(state);
+
+ const char next_char = peek(state);
+ if (next_char == '}') {
+ state->cursor++;
+ state->current_nesting--;
+ size_t count = state->stack->head - stack_head;
+
+ // Temporary rewind cursor in case an error is raised
+ const char *final_cursor = state->cursor;
+ state->cursor = object_start_cursor;
+ VALUE object = json_decode_object(state, config, count);
+ state->cursor = final_cursor;
+
+ return json_push_value(state, config, object);
+ }
+
+ if (next_char == ',') {
+ state->cursor++;
+ json_eat_whitespace(state);
+
+ if (config->allow_trailing_comma) {
+ if (peek(state) == '}') {
+ continue;
+ }
+ }
+
+ if (RB_UNLIKELY(peek(state) != '"')) {
+ raise_parse_error("expected object key, got: %s", state);
+ }
+ json_parse_string(state, config, true);
+
+ json_eat_whitespace(state);
+ if (RB_UNLIKELY(peek(state) != ':')) {
+ raise_parse_error("expected ':' after object key, got: %s", state);
+ }
+ state->cursor++;
+
+ json_parse_any(state, config);
+
+ continue;
+ }
+
+ raise_parse_error("expected ',' or '}' after object value, got: %s", state);
+ }
+ break;
+ }
+
+ case 0:
+ raise_parse_error("unexpected end of input", state);
+ break;
+
+ default:
+ raise_parse_error("unexpected character: %s", state);
+ break;
+ }
+
+ raise_parse_error("unreachable: %s", state);
+ return Qundef;
+}
+
+static void json_ensure_eof(JSON_ParserState *state)
+{
+ json_eat_whitespace(state);
+ if (!eos(state)) {
+ raise_parse_error("unexpected token at end of stream %s", state);
+ }
+}
+
+/*
+ * Document-class: JSON::Ext::Parser
+ *
+ * This is the JSON parser implemented as a C extension. It can be configured
+ * to be used by setting
+ *
+ * JSON.parser = JSON::Ext::Parser
+ *
+ * with the method parser= in JSON.
+ *
+ */
+
+static VALUE convert_encoding(VALUE source)
+{
+ StringValue(source);
+ int encindex = RB_ENCODING_GET(source);
+
+ if (RB_LIKELY(encindex == utf8_encindex)) {
+ return source;
+ }
+
+ if (encindex == binary_encindex) {
+ // For historical reason, we silently reinterpret binary strings as UTF-8
+ return rb_enc_associate_index(rb_str_dup(source), utf8_encindex);
+ }
+
+ source = rb_funcall(source, i_encode, 1, Encoding_UTF_8);
+ StringValue(source);
+ return source;
+}
+
+struct parser_config_init_args {
+ JSON_ParserConfig *config;
+ VALUE self;
+};
+
+static void parser_config_wb_write(VALUE self, VALUE *dest, VALUE val)
+{
+ *dest = val;
+ if (self) RB_OBJ_WRITTEN(self, Qundef, val);
+}
+
+static int parser_config_init_i(VALUE key, VALUE val, VALUE data)
+{
+ struct parser_config_init_args *args = (struct parser_config_init_args *)data;
+ JSON_ParserConfig *config = args->config;
+ VALUE self = args->self;
+
+ if (key == sym_max_nesting) { config->max_nesting = RTEST(val) ? FIX2INT(val) : 0; }
+ else if (key == sym_allow_nan) { config->allow_nan = RTEST(val); }
+ else if (key == sym_allow_trailing_comma) { config->allow_trailing_comma = RTEST(val); }
+ else if (key == sym_allow_control_characters) { config->allow_control_characters = RTEST(val); }
+ else if (key == sym_allow_invalid_escape) { config->allow_invalid_escape = RTEST(val); }
+ else if (key == sym_symbolize_names) { config->symbolize_names = RTEST(val); }
+ else if (key == sym_freeze) { config->freeze = RTEST(val); }
+ else if (key == sym_on_load) { parser_config_wb_write(self, &config->on_load_proc, RTEST(val) ? val : Qfalse); }
+ else if (key == sym_allow_duplicate_key) { config->on_duplicate_key = RTEST(val) ? JSON_IGNORE : JSON_RAISE; }
+ else if (key == sym_decimal_class) {
+ if (RTEST(val)) {
+ if (rb_respond_to(val, i_try_convert)) {
+ parser_config_wb_write(self, &config->decimal_class, val);
+ config->decimal_method_id = i_try_convert;
+ } else if (rb_respond_to(val, i_new)) {
+ parser_config_wb_write(self, &config->decimal_class, val);
+ config->decimal_method_id = i_new;
+ } else if (RB_TYPE_P(val, T_CLASS)) {
+ VALUE name = rb_class_name(val);
+ const char *name_cstr = RSTRING_PTR(name);
+ const char *last_colon = strrchr(name_cstr, ':');
+ if (last_colon) {
+ const char *mod_path_end = last_colon - 1;
+ VALUE mod_path = rb_str_substr(name, 0, mod_path_end - name_cstr);
+ parser_config_wb_write(self, &config->decimal_class, rb_path_to_class(mod_path));
+
+ const char *method_name_beg = last_colon + 1;
+ long before_len = method_name_beg - name_cstr;
+ long len = RSTRING_LEN(name) - before_len;
+ VALUE method_name = rb_str_substr(name, before_len, len);
+ config->decimal_method_id = SYM2ID(rb_str_intern(method_name));
+ } else {
+ parser_config_wb_write(self, &config->decimal_class, rb_mKernel);
+ config->decimal_method_id = SYM2ID(rb_str_intern(name));
+ }
+ }
+ }
+ }
+
+ return ST_CONTINUE;
+}
+
+static void parser_config_init(JSON_ParserConfig *config, VALUE opts, VALUE self)
+{
+ config->max_nesting = 100;
+
+ struct parser_config_init_args args = {
+ .config = config,
+ .self = self,
+ };
+
+ if (!NIL_P(opts)) {
+ Check_Type(opts, T_HASH);
+ if (RHASH_SIZE(opts) > 0) {
+ // We assume in most cases few keys are set so it's faster to go over
+ // the provided keys than to check all possible keys.
+ rb_hash_foreach(opts, parser_config_init_i, (VALUE)&args);
+ }
+
+ }
+}
+
+/*
+ * call-seq: new(opts => {})
+ *
+ * Creates a new JSON::Ext::ParserConfig instance.
+ *
+ * It will be configured by the _opts_ hash. _opts_ can have the following
+ * keys:
+ *
+ * _opts_ can have the following keys:
+ * * *max_nesting*: The maximum depth of nesting allowed in the parsed data
+ * structures. Disable depth checking with :max_nesting => false|nil|0, it
+ * defaults to 100.
+ * * *allow_nan*: If set to true, allow NaN, Infinity and -Infinity in
+ * defiance of RFC 4627 to be parsed by the Parser. This option defaults to
+ * false.
+ * * *symbolize_names*: If set to true, returns symbols for the names
+ * (keys) in a JSON object. Otherwise strings are returned, which is
+ * also the default. It's not possible to use this option in
+ * conjunction with the *create_additions* option.
+ * * *decimal_class*: Specifies which class to use instead of the default
+ * (Float) when parsing decimal numbers. This class must accept a single
+ * string argument in its constructor.
+ */
+static VALUE cParserConfig_initialize(VALUE self, VALUE opts)
+{
+ rb_check_frozen(self);
+ GET_PARSER_CONFIG;
+
+ parser_config_init(config, opts, self);
+
+ return self;
+}
+
+static VALUE cParser_parse(JSON_ParserConfig *config, VALUE src)
+{
+ VALUE Vsource = convert_encoding(src);
+
+ // Ensure the string isn't mutated under us.
+ // The classic API to use is `rb_str_locktmp`, but then we'd
+ // need to use `rb_protect` to make sure we always unlock.
+ if (Vsource == src) {
+ Vsource = rb_str_new_frozen(Vsource);
+ }
+
+ VALUE rvalue_stack_buffer[RVALUE_STACK_INITIAL_CAPA];
+ rvalue_stack stack = {
+ .type = RVALUE_STACK_STACK_ALLOCATED,
+ .ptr = rvalue_stack_buffer,
+ .capa = RVALUE_STACK_INITIAL_CAPA,
+ };
+
+ long len;
+ const char *start;
+
+ RSTRING_GETMEM(Vsource, start, len);
+
+ VALUE stack_handle = 0;
+ JSON_ParserState _state = {
+ .start = start,
+ .cursor = start,
+ .end = start + len,
+ .stack = &stack,
+ .stack_handle = &stack_handle,
+ };
+ JSON_ParserState *state = &_state;
+
+ VALUE result = json_parse_any(state, config);
+
+ // This may be skipped in case of exception, but
+ // it won't cause a leak.
+ rvalue_stack_eagerly_release(stack_handle);
+ RB_GC_GUARD(stack_handle);
+ RB_GC_GUARD(Vsource);
+ json_ensure_eof(state);
+
+ return result;
+}
+
+/*
+ * call-seq: parse(source)
+ *
+ * Parses the current JSON text _source_ and returns the complete data
+ * structure as a result.
+ * It raises JSON::ParserError if fail to parse.
+ */
+static VALUE cParserConfig_parse(VALUE self, VALUE Vsource)
+{
+ GET_PARSER_CONFIG;
+ return cParser_parse(config, Vsource);
+}
+
+static VALUE cParser_m_parse(VALUE klass, VALUE Vsource, VALUE opts)
+{
+ JSON_ParserConfig _config = {0};
+ JSON_ParserConfig *config = &_config;
+ parser_config_init(config, opts, false);
+
+ return cParser_parse(config, Vsource);
+}
+
+static void JSON_ParserConfig_mark(void *ptr)
+{
+ JSON_ParserConfig *config = ptr;
+ rb_gc_mark(config->on_load_proc);
+ rb_gc_mark(config->decimal_class);
+}
+
+static size_t JSON_ParserConfig_memsize(const void *ptr)
+{
+ return sizeof(JSON_ParserConfig);
+}
+
+static const rb_data_type_t JSON_ParserConfig_type = {
+ .wrap_struct_name = "JSON::Ext::Parser/ParserConfig",
+ .function = {
+ JSON_ParserConfig_mark,
+ RUBY_DEFAULT_FREE,
+ JSON_ParserConfig_memsize,
+ },
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_EMBEDDABLE,
+};
+
+static VALUE cJSON_parser_s_allocate(VALUE klass)
+{
+ JSON_ParserConfig *config;
+ return TypedData_Make_Struct(klass, JSON_ParserConfig, &JSON_ParserConfig_type, config);
+}
+
+void Init_parser(void)
+{
+#ifdef HAVE_RB_EXT_RACTOR_SAFE
+ rb_ext_ractor_safe(true);
+#endif
+
+#undef rb_intern
+ rb_require("json/common");
+ mJSON = rb_define_module("JSON");
+ VALUE mExt = rb_define_module_under(mJSON, "Ext");
+ VALUE cParserConfig = rb_define_class_under(mExt, "ParserConfig", rb_cObject);
+ eNestingError = rb_path2class("JSON::NestingError");
+ rb_gc_register_mark_object(eNestingError);
+ rb_define_alloc_func(cParserConfig, cJSON_parser_s_allocate);
+ rb_define_method(cParserConfig, "initialize", cParserConfig_initialize, 1);
+ rb_define_method(cParserConfig, "parse", cParserConfig_parse, 1);
+
+ VALUE cParser = rb_define_class_under(mExt, "Parser", rb_cObject);
+ rb_define_singleton_method(cParser, "parse", cParser_m_parse, 2);
+
+ CNaN = rb_const_get(mJSON, rb_intern("NaN"));
+ rb_gc_register_mark_object(CNaN);
+
+ CInfinity = rb_const_get(mJSON, rb_intern("Infinity"));
+ rb_gc_register_mark_object(CInfinity);
+
+ CMinusInfinity = rb_const_get(mJSON, rb_intern("MinusInfinity"));
+ rb_gc_register_mark_object(CMinusInfinity);
+
+ rb_global_variable(&Encoding_UTF_8);
+ Encoding_UTF_8 = rb_const_get(rb_path2class("Encoding"), rb_intern("UTF_8"));
+
+ sym_max_nesting = ID2SYM(rb_intern("max_nesting"));
+ sym_allow_nan = ID2SYM(rb_intern("allow_nan"));
+ sym_allow_trailing_comma = ID2SYM(rb_intern("allow_trailing_comma"));
+ sym_allow_control_characters = ID2SYM(rb_intern("allow_control_characters"));
+ sym_allow_invalid_escape = ID2SYM(rb_intern("allow_invalid_escape"));
+ sym_symbolize_names = ID2SYM(rb_intern("symbolize_names"));
+ sym_freeze = ID2SYM(rb_intern("freeze"));
+ sym_on_load = ID2SYM(rb_intern("on_load"));
+ sym_decimal_class = ID2SYM(rb_intern("decimal_class"));
+ sym_allow_duplicate_key = ID2SYM(rb_intern("allow_duplicate_key"));
+
+ i_new = rb_intern("new");
+ i_try_convert = rb_intern("try_convert");
+ i_uminus = rb_intern("-@");
+ i_encode = rb_intern("encode");
+
+ binary_encindex = rb_ascii8bit_encindex();
+ utf8_encindex = rb_utf8_encindex();
+ enc_utf8 = rb_utf8_encoding();
+
+#ifdef HAVE_SIMD
+ simd_impl = find_simd_implementation();
+#endif
+}
diff --git a/ext/json/simd/conf.rb b/ext/json/simd/conf.rb
new file mode 100644
index 0000000000..76f774bc97
--- /dev/null
+++ b/ext/json/simd/conf.rb
@@ -0,0 +1,24 @@
+case RbConfig::CONFIG['host_cpu']
+when /^(arm|aarch64)/
+ # Try to compile a small program using NEON instructions
+ header, type, init, extra = 'arm_neon.h', 'uint8x16_t', 'vdupq_n_u8(32)', nil
+when /^(x86_64|x64)/
+ header, type, init, extra = 'x86intrin.h', '__m128i', '_mm_set1_epi8(32)', 'if (__builtin_cpu_supports("sse2")) { printf("OK"); }'
+end
+if header
+ if have_header(header) && try_compile(<<~SRC, '-Werror=implicit-function-declaration')
+ #{cpp_include(header)}
+ int main(int argc, char **argv) {
+ #{type} test = #{init};
+ #{extra}
+ if (argc > 100000) printf("%p", &test);
+ return 0;
+ }
+ SRC
+ $defs.push("-DJSON_ENABLE_SIMD")
+ else
+ puts "Disable SIMD"
+ end
+end
+
+have_header('cpuid.h')
diff --git a/ext/json/simd/simd.h b/ext/json/simd/simd.h
new file mode 100644
index 0000000000..611b41b066
--- /dev/null
+++ b/ext/json/simd/simd.h
@@ -0,0 +1,208 @@
+#include "../json.h"
+
+typedef enum {
+ SIMD_NONE,
+ SIMD_NEON,
+ SIMD_SSE2
+} SIMD_Implementation;
+
+#ifndef __has_builtin // Optional of course.
+ #define __has_builtin(x) 0 // Compatibility with non-clang compilers.
+#endif
+
+#ifdef __clang__
+# if __has_builtin(__builtin_ctzll)
+# define HAVE_BUILTIN_CTZLL 1
+# else
+# define HAVE_BUILTIN_CTZLL 0
+# endif
+#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))
+# define HAVE_BUILTIN_CTZLL 1
+#else
+# define HAVE_BUILTIN_CTZLL 0
+#endif
+
+static inline uint32_t trailing_zeros64(uint64_t input)
+{
+ JSON_ASSERT(input > 0); // __builtin_ctz(0) is undefined behavior
+
+#if HAVE_BUILTIN_CTZLL
+ return __builtin_ctzll(input);
+#else
+ uint32_t trailing_zeros = 0;
+ uint64_t temp = input;
+ while ((temp & 1) == 0 && temp > 0) {
+ trailing_zeros++;
+ temp >>= 1;
+ }
+ return trailing_zeros;
+#endif
+}
+
+static inline int trailing_zeros(int input)
+{
+ JSON_ASSERT(input > 0); // __builtin_ctz(0) is undefined behavior
+
+#if HAVE_BUILTIN_CTZLL
+ return __builtin_ctz(input);
+#else
+ int trailing_zeros = 0;
+ int temp = input;
+ while ((temp & 1) == 0 && temp > 0) {
+ trailing_zeros++;
+ temp >>= 1;
+ }
+ return trailing_zeros;
+#endif
+}
+
+#ifdef JSON_ENABLE_SIMD
+
+#define SIMD_MINIMUM_THRESHOLD 4
+
+ALWAYS_INLINE(static) void json_fast_memcpy16(char *dst, const char *src, size_t len)
+{
+ RBIMPL_ASSERT_OR_ASSUME(len < 16);
+ RBIMPL_ASSERT_OR_ASSUME(len >= SIMD_MINIMUM_THRESHOLD); // 4
+#if defined(__has_builtin) && __has_builtin(__builtin_memcpy)
+ // If __builtin_memcpy is available, use it to copy between SIMD_MINIMUM_THRESHOLD (4) and vec_len-1 (15) bytes.
+ // These copies overlap. The first copy will copy the first 8 (or 4) bytes. The second copy will copy
+ // the last 8 (or 4) bytes but overlap with the first copy. The overlapping bytes will be in the correct
+ // position in both copies.
+
+ // Please do not attempt to replace __builtin_memcpy with memcpy without profiling and/or looking at the
+ // generated assembly. On clang-specifically (tested on Apple clang version 17.0.0 (clang-1700.0.13.3)),
+ // when using memcpy, the compiler will notice the only difference is a 4 or 8 and generate a conditional
+ // select instruction instead of direct loads and stores with a branch. This ends up slower than the branch
+ // plus two loads and stores generated when using __builtin_memcpy.
+ if (len >= 8) {
+ __builtin_memcpy(dst, src, 8);
+ __builtin_memcpy(dst + len - 8, src + len - 8, 8);
+ } else {
+ __builtin_memcpy(dst, src, 4);
+ __builtin_memcpy(dst + len - 4, src + len - 4, 4);
+ }
+#else
+ MEMCPY(dst, src, char, len);
+#endif
+}
+
+#if defined(__ARM_NEON) || defined(__ARM_NEON__) || defined(__aarch64__) || defined(_M_ARM64)
+#include <arm_neon.h>
+
+#define FIND_SIMD_IMPLEMENTATION_DEFINED 1
+static inline SIMD_Implementation find_simd_implementation(void)
+{
+ return SIMD_NEON;
+}
+
+#define HAVE_SIMD 1
+#define HAVE_SIMD_NEON 1
+
+// See: https://community.arm.com/arm-community-blogs/b/servers-and-cloud-computing-blog/posts/porting-x86-vector-bitmask-optimizations-to-arm-neon
+ALWAYS_INLINE(static) uint64_t neon_match_mask(uint8x16_t matches)
+{
+ const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(matches), 4);
+ const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0);
+ return mask & 0x8888888888888888ull;
+}
+
+ALWAYS_INLINE(static) uint64_t compute_chunk_mask_neon(const char *ptr)
+{
+ uint8x16_t chunk = vld1q_u8((const unsigned char *)ptr);
+
+ // Trick: c < 32 || c == 34 can be factored as c ^ 2 < 33
+ // https://lemire.me/blog/2025/04/13/detect-control-characters-quotes-and-backslashes-efficiently-using-swar/
+ const uint8x16_t too_low_or_dbl_quote = vcltq_u8(veorq_u8(chunk, vdupq_n_u8(2)), vdupq_n_u8(33));
+
+ uint8x16_t has_backslash = vceqq_u8(chunk, vdupq_n_u8('\\'));
+ uint8x16_t needs_escape = vorrq_u8(too_low_or_dbl_quote, has_backslash);
+ return neon_match_mask(needs_escape);
+}
+
+ALWAYS_INLINE(static) int string_scan_simd_neon(const char **ptr, const char *end, uint64_t *mask)
+{
+ while (*ptr + sizeof(uint8x16_t) <= end) {
+ uint64_t chunk_mask = compute_chunk_mask_neon(*ptr);
+ if (chunk_mask) {
+ *mask = chunk_mask;
+ return 1;
+ }
+ *ptr += sizeof(uint8x16_t);
+ }
+ return 0;
+}
+
+#endif /* ARM Neon Support.*/
+
+#if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || defined(_M_AMD64)
+
+#ifdef HAVE_X86INTRIN_H
+#include <x86intrin.h>
+
+#define HAVE_SIMD 1
+#define HAVE_SIMD_SSE2 1
+
+#ifdef HAVE_CPUID_H
+#define FIND_SIMD_IMPLEMENTATION_DEFINED 1
+
+#if defined(__clang__) || defined(__GNUC__)
+#define TARGET_SSE2 __attribute__((target("sse2")))
+#else
+#define TARGET_SSE2
+#endif
+
+#define _mm_cmpge_epu8(a, b) _mm_cmpeq_epi8(_mm_max_epu8(a, b), a)
+#define _mm_cmple_epu8(a, b) _mm_cmpge_epu8(b, a)
+#define _mm_cmpgt_epu8(a, b) _mm_xor_si128(_mm_cmple_epu8(a, b), _mm_set1_epi8(-1))
+#define _mm_cmplt_epu8(a, b) _mm_cmpgt_epu8(b, a)
+
+ALWAYS_INLINE(static) TARGET_SSE2 int compute_chunk_mask_sse2(const char *ptr)
+{
+ __m128i chunk = _mm_loadu_si128((__m128i const*)ptr);
+ // Trick: c < 32 || c == 34 can be factored as c ^ 2 < 33
+ // https://lemire.me/blog/2025/04/13/detect-control-characters-quotes-and-backslashes-efficiently-using-swar/
+ __m128i too_low_or_dbl_quote = _mm_cmplt_epu8(_mm_xor_si128(chunk, _mm_set1_epi8(2)), _mm_set1_epi8(33));
+ __m128i has_backslash = _mm_cmpeq_epi8(chunk, _mm_set1_epi8('\\'));
+ __m128i needs_escape = _mm_or_si128(too_low_or_dbl_quote, has_backslash);
+ return _mm_movemask_epi8(needs_escape);
+}
+
+ALWAYS_INLINE(static) TARGET_SSE2 int string_scan_simd_sse2(const char **ptr, const char *end, int *mask)
+{
+ while (*ptr + sizeof(__m128i) <= end) {
+ int chunk_mask = compute_chunk_mask_sse2(*ptr);
+ if (chunk_mask) {
+ *mask = chunk_mask;
+ return 1;
+ }
+ *ptr += sizeof(__m128i);
+ }
+
+ return 0;
+}
+
+#include <cpuid.h>
+#endif /* HAVE_CPUID_H */
+
+static inline SIMD_Implementation find_simd_implementation(void)
+{
+ // TODO Revisit. I think the SSE version now only uses SSE2 instructions.
+ if (__builtin_cpu_supports("sse2")) {
+ return SIMD_SSE2;
+ }
+
+ return SIMD_NONE;
+}
+
+#endif /* HAVE_X86INTRIN_H */
+#endif /* X86_64 Support */
+
+#endif /* JSON_ENABLE_SIMD */
+
+#ifndef FIND_SIMD_IMPLEMENTATION_DEFINED
+static inline SIMD_Implementation find_simd_implementation(void)
+{
+ return SIMD_NONE;
+}
+#endif
diff --git a/ext/json/vendor/fpconv.c b/ext/json/vendor/fpconv.c
new file mode 100644
index 0000000000..6c9bc2c103
--- /dev/null
+++ b/ext/json/vendor/fpconv.c
@@ -0,0 +1,480 @@
+// Boost Software License - Version 1.0 - August 17th, 2003
+//
+// Permission is hereby granted, free of charge, to any person or organization
+// obtaining a copy of the software and accompanying documentation covered by
+// this license (the "Software") to use, reproduce, display, distribute,
+// execute, and transmit the Software, and to prepare derivative works of the
+// Software, and to permit third-parties to whom the Software is furnished to
+// do so, all subject to the following:
+//
+// The copyright notices in the Software and this entire statement, including
+// the above license grant, this restriction and the following disclaimer,
+// must be included in all copies of the Software, in whole or in part, and
+// all derivative works of the Software, unless such copies or derivative
+// works are solely in the form of machine-executable object code generated by
+// a source language processor.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+// The contents of this file is extracted from https://github.com/night-shift/fpconv
+// It was slightly modified to append ".0" to plain floats, for use with the https://github.com/ruby/json package.
+
+#include <stdbool.h>
+#include <string.h>
+#include <stdint.h>
+
+#if JSON_DEBUG
+#include <assert.h>
+#endif
+
+#define npowers 87
+#define steppowers 8
+#define firstpower -348 /* 10 ^ -348 */
+
+#define expmax -32
+#define expmin -60
+
+typedef struct Fp {
+ uint64_t frac;
+ int exp;
+} Fp;
+
+static const Fp powers_ten[] = {
+ { 18054884314459144840U, -1220 }, { 13451937075301367670U, -1193 },
+ { 10022474136428063862U, -1166 }, { 14934650266808366570U, -1140 },
+ { 11127181549972568877U, -1113 }, { 16580792590934885855U, -1087 },
+ { 12353653155963782858U, -1060 }, { 18408377700990114895U, -1034 },
+ { 13715310171984221708U, -1007 }, { 10218702384817765436U, -980 },
+ { 15227053142812498563U, -954 }, { 11345038669416679861U, -927 },
+ { 16905424996341287883U, -901 }, { 12595523146049147757U, -874 },
+ { 9384396036005875287U, -847 }, { 13983839803942852151U, -821 },
+ { 10418772551374772303U, -794 }, { 15525180923007089351U, -768 },
+ { 11567161174868858868U, -741 }, { 17236413322193710309U, -715 },
+ { 12842128665889583758U, -688 }, { 9568131466127621947U, -661 },
+ { 14257626930069360058U, -635 }, { 10622759856335341974U, -608 },
+ { 15829145694278690180U, -582 }, { 11793632577567316726U, -555 },
+ { 17573882009934360870U, -529 }, { 13093562431584567480U, -502 },
+ { 9755464219737475723U, -475 }, { 14536774485912137811U, -449 },
+ { 10830740992659433045U, -422 }, { 16139061738043178685U, -396 },
+ { 12024538023802026127U, -369 }, { 17917957937422433684U, -343 },
+ { 13349918974505688015U, -316 }, { 9946464728195732843U, -289 },
+ { 14821387422376473014U, -263 }, { 11042794154864902060U, -236 },
+ { 16455045573212060422U, -210 }, { 12259964326927110867U, -183 },
+ { 18268770466636286478U, -157 }, { 13611294676837538539U, -130 },
+ { 10141204801825835212U, -103 }, { 15111572745182864684U, -77 },
+ { 11258999068426240000U, -50 }, { 16777216000000000000U, -24 },
+ { 12500000000000000000U, 3 }, { 9313225746154785156U, 30 },
+ { 13877787807814456755U, 56 }, { 10339757656912845936U, 83 },
+ { 15407439555097886824U, 109 }, { 11479437019748901445U, 136 },
+ { 17105694144590052135U, 162 }, { 12744735289059618216U, 189 },
+ { 9495567745759798747U, 216 }, { 14149498560666738074U, 242 },
+ { 10542197943230523224U, 269 }, { 15709099088952724970U, 295 },
+ { 11704190886730495818U, 322 }, { 17440603504673385349U, 348 },
+ { 12994262207056124023U, 375 }, { 9681479787123295682U, 402 },
+ { 14426529090290212157U, 428 }, { 10748601772107342003U, 455 },
+ { 16016664761464807395U, 481 }, { 11933345169920330789U, 508 },
+ { 17782069995880619868U, 534 }, { 13248674568444952270U, 561 },
+ { 9871031767461413346U, 588 }, { 14708983551653345445U, 614 },
+ { 10959046745042015199U, 641 }, { 16330252207878254650U, 667 },
+ { 12166986024289022870U, 694 }, { 18130221999122236476U, 720 },
+ { 13508068024458167312U, 747 }, { 10064294952495520794U, 774 },
+ { 14996968138956309548U, 800 }, { 11173611982879273257U, 827 },
+ { 16649979327439178909U, 853 }, { 12405201291620119593U, 880 },
+ { 9242595204427927429U, 907 }, { 13772540099066387757U, 933 },
+ { 10261342003245940623U, 960 }, { 15290591125556738113U, 986 },
+ { 11392378155556871081U, 1013 }, { 16975966327722178521U, 1039 },
+ { 12648080533535911531U, 1066 }
+};
+
+static Fp find_cachedpow10(int exp, int* k)
+{
+ const double one_log_ten = 0.30102999566398114;
+
+ int approx = (int)(-(exp + npowers) * one_log_ten);
+ int idx = (approx - firstpower) / steppowers;
+
+ while(1) {
+ int current = exp + powers_ten[idx].exp + 64;
+
+ if(current < expmin) {
+ idx++;
+ continue;
+ }
+
+ if(current > expmax) {
+ idx--;
+ continue;
+ }
+
+ *k = (firstpower + idx * steppowers);
+
+ return powers_ten[idx];
+ }
+}
+
+#define fracmask 0x000FFFFFFFFFFFFFU
+#define expmask 0x7FF0000000000000U
+#define hiddenbit 0x0010000000000000U
+#define signmask 0x8000000000000000U
+#define expbias (1023 + 52)
+
+#define absv(n) ((n) < 0 ? -(n) : (n))
+#define minv(a, b) ((a) < (b) ? (a) : (b))
+
+static const uint64_t tens[] = {
+ 10000000000000000000U, 1000000000000000000U, 100000000000000000U,
+ 10000000000000000U, 1000000000000000U, 100000000000000U,
+ 10000000000000U, 1000000000000U, 100000000000U,
+ 10000000000U, 1000000000U, 100000000U,
+ 10000000U, 1000000U, 100000U,
+ 10000U, 1000U, 100U,
+ 10U, 1U
+};
+
+static inline uint64_t get_dbits(double d)
+{
+ union {
+ double dbl;
+ uint64_t i;
+ } dbl_bits = { d };
+
+ return dbl_bits.i;
+}
+
+static Fp build_fp(double d)
+{
+ uint64_t bits = get_dbits(d);
+
+ Fp fp;
+ fp.frac = bits & fracmask;
+ fp.exp = (bits & expmask) >> 52;
+
+ if(fp.exp) {
+ fp.frac += hiddenbit;
+ fp.exp -= expbias;
+
+ } else {
+ fp.exp = -expbias + 1;
+ }
+
+ return fp;
+}
+
+static void normalize(Fp* fp)
+{
+ while ((fp->frac & hiddenbit) == 0) {
+ fp->frac <<= 1;
+ fp->exp--;
+ }
+
+ int shift = 64 - 52 - 1;
+ fp->frac <<= shift;
+ fp->exp -= shift;
+}
+
+static void get_normalized_boundaries(Fp* fp, Fp* lower, Fp* upper)
+{
+ upper->frac = (fp->frac << 1) + 1;
+ upper->exp = fp->exp - 1;
+
+ while ((upper->frac & (hiddenbit << 1)) == 0) {
+ upper->frac <<= 1;
+ upper->exp--;
+ }
+
+ int u_shift = 64 - 52 - 2;
+
+ upper->frac <<= u_shift;
+ upper->exp = upper->exp - u_shift;
+
+
+ int l_shift = fp->frac == hiddenbit ? 2 : 1;
+
+ lower->frac = (fp->frac << l_shift) - 1;
+ lower->exp = fp->exp - l_shift;
+
+
+ lower->frac <<= lower->exp - upper->exp;
+ lower->exp = upper->exp;
+}
+
+static Fp multiply(Fp* a, Fp* b)
+{
+ const uint64_t lomask = 0x00000000FFFFFFFF;
+
+ uint64_t ah_bl = (a->frac >> 32) * (b->frac & lomask);
+ uint64_t al_bh = (a->frac & lomask) * (b->frac >> 32);
+ uint64_t al_bl = (a->frac & lomask) * (b->frac & lomask);
+ uint64_t ah_bh = (a->frac >> 32) * (b->frac >> 32);
+
+ uint64_t tmp = (ah_bl & lomask) + (al_bh & lomask) + (al_bl >> 32);
+ /* round up */
+ tmp += 1U << 31;
+
+ Fp fp = {
+ ah_bh + (ah_bl >> 32) + (al_bh >> 32) + (tmp >> 32),
+ a->exp + b->exp + 64
+ };
+
+ return fp;
+}
+
+static void round_digit(char* digits, int ndigits, uint64_t delta, uint64_t rem, uint64_t kappa, uint64_t frac)
+{
+ while (rem < frac && delta - rem >= kappa &&
+ (rem + kappa < frac || frac - rem > rem + kappa - frac)) {
+
+ digits[ndigits - 1]--;
+ rem += kappa;
+ }
+}
+
+static int generate_digits(Fp* fp, Fp* upper, Fp* lower, char* digits, int* K)
+{
+ uint64_t wfrac = upper->frac - fp->frac;
+ uint64_t delta = upper->frac - lower->frac;
+
+ Fp one;
+ one.frac = 1ULL << -upper->exp;
+ one.exp = upper->exp;
+
+ uint64_t part1 = upper->frac >> -one.exp;
+ uint64_t part2 = upper->frac & (one.frac - 1);
+
+ int idx = 0, kappa = 10;
+ const uint64_t* divp;
+ /* 1000000000 */
+ for(divp = tens + 10; kappa > 0; divp++) {
+
+ uint64_t div = *divp;
+ unsigned digit = (unsigned) (part1 / div);
+
+ if (digit || idx) {
+ digits[idx++] = digit + '0';
+ }
+
+ part1 -= digit * div;
+ kappa--;
+
+ uint64_t tmp = (part1 <<-one.exp) + part2;
+ if (tmp <= delta) {
+ *K += kappa;
+ round_digit(digits, idx, delta, tmp, div << -one.exp, wfrac);
+
+ return idx;
+ }
+ }
+
+ /* 10 */
+ const uint64_t* unit = tens + 18;
+
+ while(true) {
+ part2 *= 10;
+ delta *= 10;
+ kappa--;
+
+ unsigned digit = (unsigned) (part2 >> -one.exp);
+ if (digit || idx) {
+ digits[idx++] = digit + '0';
+ }
+
+ part2 &= one.frac - 1;
+ if (part2 < delta) {
+ *K += kappa;
+ round_digit(digits, idx, delta, part2, one.frac, wfrac * *unit);
+
+ return idx;
+ }
+
+ unit--;
+ }
+}
+
+static int grisu2(double d, char* digits, int* K)
+{
+ Fp w = build_fp(d);
+
+ Fp lower, upper;
+ get_normalized_boundaries(&w, &lower, &upper);
+
+ normalize(&w);
+
+ int k;
+ Fp cp = find_cachedpow10(upper.exp, &k);
+
+ w = multiply(&w, &cp);
+ upper = multiply(&upper, &cp);
+ lower = multiply(&lower, &cp);
+
+ lower.frac++;
+ upper.frac--;
+
+ *K = -k;
+
+ return generate_digits(&w, &upper, &lower, digits, K);
+}
+
+static int emit_digits(char* digits, int ndigits, char* dest, int K, bool neg)
+{
+ int exp = absv(K + ndigits - 1);
+
+ if(K >= 0 && exp < 15) {
+ memcpy(dest, digits, ndigits);
+ memset(dest + ndigits, '0', K);
+
+ /* add a .0 to mark this as a float. */
+ dest[ndigits + K] = '.';
+ dest[ndigits + K + 1] = '0';
+
+ return ndigits + K + 2;
+ }
+
+ /* write decimal w/o scientific notation */
+ if(K < 0 && (K > -7 || exp < 10)) {
+ int offset = ndigits - absv(K);
+ /* fp < 1.0 -> write leading zero */
+ if(offset <= 0) {
+ offset = -offset;
+ dest[0] = '0';
+ dest[1] = '.';
+ memset(dest + 2, '0', offset);
+ memcpy(dest + offset + 2, digits, ndigits);
+
+ return ndigits + 2 + offset;
+
+ /* fp > 1.0 */
+ } else {
+ memcpy(dest, digits, offset);
+ dest[offset] = '.';
+ memcpy(dest + offset + 1, digits + offset, ndigits - offset);
+
+ return ndigits + 1;
+ }
+ }
+
+ /* write decimal w/ scientific notation */
+ ndigits = minv(ndigits, 18 - neg);
+
+ int idx = 0;
+ dest[idx++] = digits[0];
+
+ if(ndigits > 1) {
+ dest[idx++] = '.';
+ memcpy(dest + idx, digits + 1, ndigits - 1);
+ idx += ndigits - 1;
+ }
+
+ dest[idx++] = 'e';
+
+ char sign = K + ndigits - 1 < 0 ? '-' : '+';
+ dest[idx++] = sign;
+
+ int cent = 0;
+
+ if(exp > 99) {
+ cent = exp / 100;
+ dest[idx++] = cent + '0';
+ exp -= cent * 100;
+ }
+ if(exp > 9) {
+ int dec = exp / 10;
+ dest[idx++] = dec + '0';
+ exp -= dec * 10;
+
+ } else if(cent) {
+ dest[idx++] = '0';
+ }
+
+ dest[idx++] = exp % 10 + '0';
+
+ return idx;
+}
+
+static int filter_special(double fp, char* dest)
+{
+ if(fp == 0.0) {
+ dest[0] = '0';
+ dest[1] = '.';
+ dest[2] = '0';
+ return 3;
+ }
+
+ uint64_t bits = get_dbits(fp);
+
+ bool nan = (bits & expmask) == expmask;
+
+ if(!nan) {
+ return 0;
+ }
+
+ if(bits & fracmask) {
+ dest[0] = 'n'; dest[1] = 'a'; dest[2] = 'n';
+
+ } else {
+ dest[0] = 'i'; dest[1] = 'n'; dest[2] = 'f';
+ }
+
+ return 3;
+}
+
+/* Fast and accurate double to string conversion based on Florian Loitsch's
+ * Grisu-algorithm[1].
+ *
+ * Input:
+ * fp -> the double to convert, dest -> destination buffer.
+ * The generated string will never be longer than 32 characters.
+ * Make sure to pass a pointer to at least 32 bytes of memory.
+ * The emitted string will not be null terminated.
+ *
+ *
+ *
+ * Output:
+ * The number of written characters.
+ *
+ * Exemplary usage:
+ *
+ * void print(double d)
+ * {
+ * char buf[28 + 1] // plus null terminator
+ * int str_len = fpconv_dtoa(d, buf);
+ *
+ * buf[str_len] = '\0';
+ * printf("%s", buf);
+ * }
+ *
+ */
+static int fpconv_dtoa(double d, char dest[32])
+{
+ char digits[18];
+
+ int str_len = 0;
+ bool neg = false;
+
+ if(get_dbits(d) & signmask) {
+ dest[0] = '-';
+ str_len++;
+ neg = true;
+ }
+
+ int spec = filter_special(d, dest + str_len);
+
+ if(spec) {
+ return str_len + spec;
+ }
+
+ int K = 0;
+ int ndigits = grisu2(d, digits, &K);
+
+ str_len += emit_digits(digits, ndigits, dest + str_len, K, neg);
+#if JSON_DEBUG
+ assert(str_len <= 32);
+#endif
+
+ return str_len;
+}
diff --git a/ext/json/vendor/jeaiii-ltoa.h b/ext/json/vendor/jeaiii-ltoa.h
new file mode 100644
index 0000000000..ba4f497fc8
--- /dev/null
+++ b/ext/json/vendor/jeaiii-ltoa.h
@@ -0,0 +1,267 @@
+/*
+
+This file is released under the terms of the MIT License. It is based on the
+work of James Edward Anhalt III, with the original license listed below.
+
+MIT License
+
+Copyright (c) 2024,2025 Enrico Thierbach - https://github.com/radiospiel
+Copyright (c) 2022 James Edward Anhalt III - https://github.com/jeaiii/itoa
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#ifndef JEAIII_TO_TEXT_H_
+#define JEAIII_TO_TEXT_H_
+
+#include <stdint.h>
+
+typedef uint_fast32_t u32_t;
+typedef uint_fast64_t u64_t;
+
+#define u32(x) ((u32_t)(x))
+#define u64(x) ((u64_t)(x))
+
+struct digit_pair
+{
+ char dd[2];
+};
+
+static const struct digit_pair *digits_dd = (struct digit_pair *)(
+ "00" "01" "02" "03" "04" "05" "06" "07" "08" "09"
+ "10" "11" "12" "13" "14" "15" "16" "17" "18" "19"
+ "20" "21" "22" "23" "24" "25" "26" "27" "28" "29"
+ "30" "31" "32" "33" "34" "35" "36" "37" "38" "39"
+ "40" "41" "42" "43" "44" "45" "46" "47" "48" "49"
+ "50" "51" "52" "53" "54" "55" "56" "57" "58" "59"
+ "60" "61" "62" "63" "64" "65" "66" "67" "68" "69"
+ "70" "71" "72" "73" "74" "75" "76" "77" "78" "79"
+ "80" "81" "82" "83" "84" "85" "86" "87" "88" "89"
+ "90" "91" "92" "93" "94" "95" "96" "97" "98" "99"
+);
+
+static const struct digit_pair *digits_fd = (struct digit_pair *)(
+ "0_" "1_" "2_" "3_" "4_" "5_" "6_" "7_" "8_" "9_"
+ "10" "11" "12" "13" "14" "15" "16" "17" "18" "19"
+ "20" "21" "22" "23" "24" "25" "26" "27" "28" "29"
+ "30" "31" "32" "33" "34" "35" "36" "37" "38" "39"
+ "40" "41" "42" "43" "44" "45" "46" "47" "48" "49"
+ "50" "51" "52" "53" "54" "55" "56" "57" "58" "59"
+ "60" "61" "62" "63" "64" "65" "66" "67" "68" "69"
+ "70" "71" "72" "73" "74" "75" "76" "77" "78" "79"
+ "80" "81" "82" "83" "84" "85" "86" "87" "88" "89"
+ "90" "91" "92" "93" "94" "95" "96" "97" "98" "99"
+);
+
+static const u64_t mask24 = (u64(1) << 24) - 1;
+static const u64_t mask32 = (u64(1) << 32) - 1;
+static const u64_t mask57 = (u64(1) << 57) - 1;
+
+#define COPY(buffer, digits) memcpy(buffer, &(digits), sizeof(struct digit_pair))
+
+static char *
+jeaiii_ultoa(char *b, u64_t n)
+{
+ if (n < u32(1e2)) {
+ COPY(b, digits_fd[n]);
+ return n < 10 ? b + 1 : b + 2;
+ }
+
+ if (n < u32(1e6)) {
+ if (n < u32(1e4)) {
+ u32_t f0 = u32((10 * (1 << 24) / 1e3 + 1) * n);
+ COPY(b, digits_fd[f0 >> 24]);
+
+ b -= n < u32(1e3);
+ u32_t f2 = (f0 & mask24) * 100;
+ COPY(b + 2, digits_dd[f2 >> 24]);
+
+ return b + 4;
+ }
+
+ u64_t f0 = u64(10 * (1ull << 32ull)/ 1e5 + 1) * n;
+ COPY(b, digits_fd[f0 >> 32]);
+
+ b -= n < u32(1e5);
+ u64_t f2 = (f0 & mask32) * 100;
+ COPY(b + 2, digits_dd[f2 >> 32]);
+
+ u64_t f4 = (f2 & mask32) * 100;
+ COPY(b + 4, digits_dd[f4 >> 32]);
+ return b + 6;
+ }
+
+ if (n < u64(1ull << 32ull)) {
+ if (n < u32(1e8)) {
+ u64_t f0 = u64(10 * (1ull << 48ull) / 1e7 + 1) * n >> 16;
+ COPY(b, digits_fd[f0 >> 32]);
+
+ b -= n < u32(1e7);
+ u64_t f2 = (f0 & mask32) * 100;
+ COPY(b + 2, digits_dd[f2 >> 32]);
+
+ u64_t f4 = (f2 & mask32) * 100;
+ COPY(b + 4, digits_dd[f4 >> 32]);
+
+ u64_t f6 = (f4 & mask32) * 100;
+ COPY(b + 6, digits_dd[f6 >> 32]);
+
+ return b + 8;
+ }
+
+ u64_t f0 = u64(10 * (1ull << 57ull) / 1e9 + 1) * n;
+ COPY(b, digits_fd[f0 >> 57]);
+
+ b -= n < u32(1e9);
+ u64_t f2 = (f0 & mask57) * 100;
+ COPY(b + 2, digits_dd[f2 >> 57]);
+
+ u64_t f4 = (f2 & mask57) * 100;
+ COPY(b + 4, digits_dd[f4 >> 57]);
+
+ u64_t f6 = (f4 & mask57) * 100;
+ COPY(b + 6, digits_dd[f6 >> 57]);
+
+ u64_t f8 = (f6 & mask57) * 100;
+ COPY(b + 8, digits_dd[f8 >> 57]);
+
+ return b + 10;
+ }
+
+ // if we get here U must be u64 but some compilers don't know that, so reassign n to a u64 to avoid warnings
+ u32_t z = n % u32(1e8);
+ u64_t u = n / u32(1e8);
+
+ if (u < u32(1e2)) {
+ // u can't be 1 digit (if u < 10 it would have been handled above as a 9 digit 32bit number)
+ COPY(b, digits_dd[u]);
+ b += 2;
+ }
+ else if (u < u32(1e6)) {
+ if (u < u32(1e4)) {
+ u32_t f0 = u32((10 * (1 << 24) / 1e3 + 1) * u);
+ COPY(b, digits_fd[f0 >> 24]);
+
+ b -= u < u32(1e3);
+ u32_t f2 = (f0 & mask24) * 100;
+ COPY(b + 2, digits_dd[f2 >> 24]);
+ b += 4;
+ }
+ else {
+ u64_t f0 = u64(10 * (1ull << 32ull) / 1e5 + 1) * u;
+ COPY(b, digits_fd[f0 >> 32]);
+
+ b -= u < u32(1e5);
+ u64_t f2 = (f0 & mask32) * 100;
+ COPY(b + 2, digits_dd[f2 >> 32]);
+
+ u64_t f4 = (f2 & mask32) * 100;
+ COPY(b + 4, digits_dd[f4 >> 32]);
+ b += 6;
+ }
+ }
+ else if (u < u32(1e8)) {
+ u64_t f0 = u64(10 * (1ull << 48ull) / 1e7 + 1) * u >> 16;
+ COPY(b, digits_fd[f0 >> 32]);
+
+ b -= u < u32(1e7);
+ u64_t f2 = (f0 & mask32) * 100;
+ COPY(b + 2, digits_dd[f2 >> 32]);
+
+ u64_t f4 = (f2 & mask32) * 100;
+ COPY(b + 4, digits_dd[f4 >> 32]);
+
+ u64_t f6 = (f4 & mask32) * 100;
+ COPY(b + 6, digits_dd[f6 >> 32]);
+
+ b += 8;
+ }
+ else if (u < u64(1ull << 32ull)) {
+ u64_t f0 = u64(10 * (1ull << 57ull) / 1e9 + 1) * u;
+ COPY(b, digits_fd[f0 >> 57]);
+
+ b -= u < u32(1e9);
+ u64_t f2 = (f0 & mask57) * 100;
+ COPY(b + 2, digits_dd[f2 >> 57]);
+
+ u64_t f4 = (f2 & mask57) * 100;
+ COPY(b + 4, digits_dd[f4 >> 57]);
+
+ u64_t f6 = (f4 & mask57) * 100;
+ COPY(b + 6, digits_dd[f6 >> 57]);
+
+ u64_t f8 = (f6 & mask57) * 100;
+ COPY(b + 8, digits_dd[f8 >> 57]);
+ b += 10;
+ }
+ else {
+ u32_t y = u % u32(1e8);
+ u /= u32(1e8);
+
+ // u is 2, 3, or 4 digits (if u < 10 it would have been handled above)
+ if (u < u32(1e2)) {
+ COPY(b, digits_dd[u]);
+ b += 2;
+ }
+ else {
+ u32_t f0 = u32((10 * (1 << 24) / 1e3 + 1) * u);
+ COPY(b, digits_fd[f0 >> 24]);
+
+ b -= u < u32(1e3);
+ u32_t f2 = (f0 & mask24) * 100;
+ COPY(b + 2, digits_dd[f2 >> 24]);
+
+ b += 4;
+ }
+ // do 8 digits
+ u64_t f0 = (u64((1ull << 48ull) / 1e6 + 1) * y >> 16) + 1;
+ COPY(b, digits_dd[f0 >> 32]);
+
+ u64_t f2 = (f0 & mask32) * 100;
+ COPY(b + 2, digits_dd[f2 >> 32]);
+
+ u64_t f4 = (f2 & mask32) * 100;
+ COPY(b + 4, digits_dd[f4 >> 32]);
+
+ u64_t f6 = (f4 & mask32) * 100;
+ COPY(b + 6, digits_dd[f6 >> 32]);
+ b += 8;
+ }
+
+ // do 8 digits
+ u64_t f0 = (u64((1ull << 48ull) / 1e6 + 1) * z >> 16) + 1;
+ COPY(b, digits_dd[f0 >> 32]);
+
+ u64_t f2 = (f0 & mask32) * 100;
+ COPY(b + 2, digits_dd[f2 >> 32]);
+
+ u64_t f4 = (f2 & mask32) * 100;
+ COPY(b + 4, digits_dd[f4 >> 32]);
+
+ u64_t f6 = (f4 & mask32) * 100;
+ COPY(b + 6, digits_dd[f6 >> 32]);
+
+ return b + 8;
+}
+
+#undef u32
+#undef u64
+#undef COPY
+
+#endif // JEAIII_TO_TEXT_H_
diff --git a/ext/json/vendor/ryu.h b/ext/json/vendor/ryu.h
new file mode 100644
index 0000000000..f06ec814b4
--- /dev/null
+++ b/ext/json/vendor/ryu.h
@@ -0,0 +1,819 @@
+// Copyright 2018 Ulf Adams
+//
+// The contents of this file may be used under the terms of the Apache License,
+// Version 2.0.
+//
+// Alternatively, the contents of this file may be used under the terms of
+// the Boost Software License, Version 1.0.
+//
+// Unless required by applicable law or agreed to in writing, this software
+// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.
+//
+// ---
+//
+// Apache License
+// Version 2.0, January 2004
+// http://www.apache.org/licenses/
+//
+// TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+//
+// 1. Definitions.
+//
+// "License" shall mean the terms and conditions for use, reproduction,
+// and distribution as defined by Sections 1 through 9 of this document.
+//
+// "Licensor" shall mean the copyright owner or entity authorized by
+// the copyright owner that is granting the License.
+//
+// "Legal Entity" shall mean the union of the acting entity and all
+// other entities that control, are controlled by, or are under common
+// control with that entity. For the purposes of this definition,
+// "control" means (i) the power, direct or indirect, to cause the
+// direction or management of such entity, whether by contract or
+// otherwise, or (ii) ownership of fifty percent (50%) or more of the
+// outstanding shares, or (iii) beneficial ownership of such entity.
+//
+// "You" (or "Your") shall mean an individual or Legal Entity
+// exercising permissions granted by this License.
+//
+// "Source" form shall mean the preferred form for making modifications,
+// including but not limited to software source code, documentation
+// source, and configuration files.
+//
+// "Object" form shall mean any form resulting from mechanical
+// transformation or translation of a Source form, including but
+// not limited to compiled object code, generated documentation,
+// and conversions to other media types.
+//
+// "Work" shall mean the work of authorship, whether in Source or
+// Object form, made available under the License, as indicated by a
+// copyright notice that is included in or attached to the work
+// (an example is provided in the Appendix below).
+//
+// "Derivative Works" shall mean any work, whether in Source or Object
+// form, that is based on (or derived from) the Work and for which the
+// editorial revisions, annotations, elaborations, or other modifications
+// represent, as a whole, an original work of authorship. For the purposes
+// of this License, Derivative Works shall not include works that remain
+// separable from, or merely link (or bind by name) to the interfaces of,
+// the Work and Derivative Works thereof.
+//
+// "Contribution" shall mean any work of authorship, including
+// the original version of the Work and any modifications or additions
+// to that Work or Derivative Works thereof, that is intentionally
+// submitted to Licensor for inclusion in the Work by the copyright owner
+// or by an individual or Legal Entity authorized to submit on behalf of
+// the copyright owner. For the purposes of this definition, "submitted"
+// means any form of electronic, verbal, or written communication sent
+// to the Licensor or its representatives, including but not limited to
+// communication on electronic mailing lists, source code control systems,
+// and issue tracking systems that are managed by, or on behalf of, the
+// Licensor for the purpose of discussing and improving the Work, but
+// excluding communication that is conspicuously marked or otherwise
+// designated in writing by the copyright owner as "Not a Contribution."
+//
+// "Contributor" shall mean Licensor and any individual or Legal Entity
+// on behalf of whom a Contribution has been received by Licensor and
+// subsequently incorporated within the Work.
+//
+// 2. Grant of Copyright License. Subject to the terms and conditions of
+// this License, each Contributor hereby grants to You a perpetual,
+// worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+// copyright license to reproduce, prepare Derivative Works of,
+// publicly display, publicly perform, sublicense, and distribute the
+// Work and such Derivative Works in Source or Object form.
+//
+// 3. Grant of Patent License. Subject to the terms and conditions of
+// this License, each Contributor hereby grants to You a perpetual,
+// worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+// (except as stated in this section) patent license to make, have made,
+// use, offer to sell, sell, import, and otherwise transfer the Work,
+// where such license applies only to those patent claims licensable
+// by such Contributor that are necessarily infringed by their
+// Contribution(s) alone or by combination of their Contribution(s)
+// with the Work to which such Contribution(s) was submitted. If You
+// institute patent litigation against any entity (including a
+// cross-claim or counterclaim in a lawsuit) alleging that the Work
+// or a Contribution incorporated within the Work constitutes direct
+// or contributory patent infringement, then any patent licenses
+// granted to You under this License for that Work shall terminate
+// as of the date such litigation is filed.
+//
+// 4. Redistribution. You may reproduce and distribute copies of the
+// Work or Derivative Works thereof in any medium, with or without
+// modifications, and in Source or Object form, provided that You
+// meet the following conditions:
+//
+// (a) You must give any other recipients of the Work or
+// Derivative Works a copy of this License; and
+//
+// (b) You must cause any modified files to carry prominent notices
+// stating that You changed the files; and
+//
+// (c) You must retain, in the Source form of any Derivative Works
+// that You distribute, all copyright, patent, trademark, and
+// attribution notices from the Source form of the Work,
+// excluding those notices that do not pertain to any part of
+// the Derivative Works; and
+//
+// (d) If the Work includes a "NOTICE" text file as part of its
+// distribution, then any Derivative Works that You distribute must
+// include a readable copy of the attribution notices contained
+// within such NOTICE file, excluding those notices that do not
+// pertain to any part of the Derivative Works, in at least one
+// of the following places: within a NOTICE text file distributed
+// as part of the Derivative Works; within the Source form or
+// documentation, if provided along with the Derivative Works; or,
+// within a display generated by the Derivative Works, if and
+// wherever such third-party notices normally appear. The contents
+// of the NOTICE file are for informational purposes only and
+// do not modify the License. You may add Your own attribution
+// notices within Derivative Works that You distribute, alongside
+// or as an addendum to the NOTICE text from the Work, provided
+// that such additional attribution notices cannot be construed
+// as modifying the License.
+//
+// You may add Your own copyright statement to Your modifications and
+// may provide additional or different license terms and conditions
+// for use, reproduction, or distribution of Your modifications, or
+// for any such Derivative Works as a whole, provided Your use,
+// reproduction, and distribution of the Work otherwise complies with
+// the conditions stated in this License.
+//
+// 5. Submission of Contributions. Unless You explicitly state otherwise,
+// any Contribution intentionally submitted for inclusion in the Work
+// by You to the Licensor shall be under the terms and conditions of
+// this License, without any additional terms or conditions.
+// Notwithstanding the above, nothing herein shall supersede or modify
+// the terms of any separate license agreement you may have executed
+// with Licensor regarding such Contributions.
+//
+// 6. Trademarks. This License does not grant permission to use the trade
+// names, trademarks, service marks, or product names of the Licensor,
+// except as required for reasonable and customary use in describing the
+// origin of the Work and reproducing the content of the NOTICE file.
+//
+// 7. Disclaimer of Warranty. Unless required by applicable law or
+// agreed to in writing, Licensor provides the Work (and each
+// Contributor provides its Contributions) on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+// implied, including, without limitation, any warranties or conditions
+// of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+// PARTICULAR PURPOSE. You are solely responsible for determining the
+// appropriateness of using or redistributing the Work and assume any
+// risks associated with Your exercise of permissions under this License.
+//
+// 8. Limitation of Liability. In no event and under no legal theory,
+// whether in tort (including negligence), contract, or otherwise,
+// unless required by applicable law (such as deliberate and grossly
+// negligent acts) or agreed to in writing, shall any Contributor be
+// liable to You for damages, including any direct, indirect, special,
+// incidental, or consequential damages of any character arising as a
+// result of this License or out of the use or inability to use the
+// Work (including but not limited to damages for loss of goodwill,
+// work stoppage, computer failure or malfunction, or any and all
+// other commercial damages or losses), even if such Contributor
+// has been advised of the possibility of such damages.
+//
+// 9. Accepting Warranty or Additional Liability. While redistributing
+// the Work or Derivative Works thereof, You may choose to offer,
+// and charge a fee for, acceptance of support, warranty, indemnity,
+// or other liability obligations and/or rights consistent with this
+// License. However, in accepting such obligations, You may act only
+// on Your own behalf and on Your sole responsibility, not on behalf
+// of any other Contributor, and only if You agree to indemnify,
+// defend, and hold each Contributor harmless for any liability
+// incurred by, or claims asserted against, such Contributor by reason
+// of your accepting any such warranty or additional liability.
+//
+// END OF TERMS AND CONDITIONS
+//
+// APPENDIX: How to apply the Apache License to your work.
+//
+// To apply the Apache License to your work, attach the following
+// boilerplate notice, with the fields enclosed by brackets "[]"
+// replaced with your own identifying information. (Don't include
+// the brackets!) The text should be enclosed in the appropriate
+// comment syntax for the file format. We also recommend that a
+// file or class name and description of purpose be included on the
+// same "printed page" as the copyright notice for easier
+// identification within third-party archives.
+//
+// Copyright [yyyy] [name of copyright owner]
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// ---
+//
+// Boost Software License - Version 1.0 - August 17th, 2003
+//
+// Permission is hereby granted, free of charge, to any person or organization
+// obtaining a copy of the software and accompanying documentation covered by
+// this license (the "Software") to use, reproduce, display, distribute,
+// execute, and transmit the Software, and to prepare derivative works of the
+// Software, and to permit third-parties to whom the Software is furnished to
+// do so, all subject to the following:
+//
+// The copyright notices in the Software and this entire statement, including
+// the above license grant, this restriction and the following disclaimer,
+// must be included in all copies of the Software, in whole or in part, and
+// all derivative works of the Software, unless such copies or derivative
+// works are solely in the form of machine-executable object code generated by
+// a source language processor.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+//
+// ---
+// Minimal Ryu implementation adapted for Ruby JSON gem by Josef Šimánek
+// Optimized for pre-extracted mantissa/exponent from JSON parsing
+// This is a stripped-down version containing only what's needed for
+// converting decimal mantissa+exponent to IEEE 754 double precision.
+
+#ifndef RYU_H
+#define RYU_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+
+// Detect __builtin_clzll availability (for floor_log2)
+// Note: MSVC doesn't have __builtin_clzll, so we provide a fallback
+#ifdef __clang__
+ #if __has_builtin(__builtin_clzll)
+ #define RYU_HAVE_BUILTIN_CLZLL 1
+ #else
+ #define RYU_HAVE_BUILTIN_CLZLL 0
+ #endif
+#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))
+ #define RYU_HAVE_BUILTIN_CLZLL 1
+#else
+ #define RYU_HAVE_BUILTIN_CLZLL 0
+#endif
+
+// Count leading zeros (for floor_log2)
+static inline uint32_t ryu_leading_zeros64(uint64_t input)
+{
+#if RYU_HAVE_BUILTIN_CLZLL
+ return __builtin_clzll(input);
+#else
+ // Fallback: binary search for the highest set bit
+ // This works on MSVC and other compilers without __builtin_clzll
+ if (input == 0) return 64;
+ uint32_t n = 0;
+ if (input <= 0x00000000FFFFFFFFULL) { n += 32; input <<= 32; }
+ if (input <= 0x0000FFFFFFFFFFFFULL) { n += 16; input <<= 16; }
+ if (input <= 0x00FFFFFFFFFFFFFFULL) { n += 8; input <<= 8; }
+ if (input <= 0x0FFFFFFFFFFFFFFFULL) { n += 4; input <<= 4; }
+ if (input <= 0x3FFFFFFFFFFFFFFFULL) { n += 2; input <<= 2; }
+ if (input <= 0x7FFFFFFFFFFFFFFFULL) { n += 1; }
+ return n;
+#endif
+}
+
+// These tables are generated by PrintDoubleLookupTable.
+#define DOUBLE_POW5_INV_BITCOUNT 125
+#define DOUBLE_POW5_BITCOUNT 125
+
+#define DOUBLE_POW5_INV_TABLE_SIZE 342
+#define DOUBLE_POW5_TABLE_SIZE 326
+
+static const uint64_t DOUBLE_POW5_INV_SPLIT[DOUBLE_POW5_INV_TABLE_SIZE][2] = {
+ { 1u, 2305843009213693952u }, { 11068046444225730970u, 1844674407370955161u },
+ { 5165088340638674453u, 1475739525896764129u }, { 7821419487252849886u, 1180591620717411303u },
+ { 8824922364862649494u, 1888946593147858085u }, { 7059937891890119595u, 1511157274518286468u },
+ { 13026647942995916322u, 1208925819614629174u }, { 9774590264567735146u, 1934281311383406679u },
+ { 11509021026396098440u, 1547425049106725343u }, { 16585914450600699399u, 1237940039285380274u },
+ { 15469416676735388068u, 1980704062856608439u }, { 16064882156130220778u, 1584563250285286751u },
+ { 9162556910162266299u, 1267650600228229401u }, { 7281393426775805432u, 2028240960365167042u },
+ { 16893161185646375315u, 1622592768292133633u }, { 2446482504291369283u, 1298074214633706907u },
+ { 7603720821608101175u, 2076918743413931051u }, { 2393627842544570617u, 1661534994731144841u },
+ { 16672297533003297786u, 1329227995784915872u }, { 11918280793837635165u, 2126764793255865396u },
+ { 5845275820328197809u, 1701411834604692317u }, { 15744267100488289217u, 1361129467683753853u },
+ { 3054734472329800808u, 2177807148294006166u }, { 17201182836831481939u, 1742245718635204932u },
+ { 6382248639981364905u, 1393796574908163946u }, { 2832900194486363201u, 2230074519853062314u },
+ { 5955668970331000884u, 1784059615882449851u }, { 1075186361522890384u, 1427247692705959881u },
+ { 12788344622662355584u, 2283596308329535809u }, { 13920024512871794791u, 1826877046663628647u },
+ { 3757321980813615186u, 1461501637330902918u }, { 10384555214134712795u, 1169201309864722334u },
+ { 5547241898389809503u, 1870722095783555735u }, { 4437793518711847602u, 1496577676626844588u },
+ { 10928932444453298728u, 1197262141301475670u }, { 17486291911125277965u, 1915619426082361072u },
+ { 6610335899416401726u, 1532495540865888858u }, { 12666966349016942027u, 1225996432692711086u },
+ { 12888448528943286597u, 1961594292308337738u }, { 17689456452638449924u, 1569275433846670190u },
+ { 14151565162110759939u, 1255420347077336152u }, { 7885109000409574610u, 2008672555323737844u },
+ { 9997436015069570011u, 1606938044258990275u }, { 7997948812055656009u, 1285550435407192220u },
+ { 12796718099289049614u, 2056880696651507552u }, { 2858676849947419045u, 1645504557321206042u },
+ { 13354987924183666206u, 1316403645856964833u }, { 17678631863951955605u, 2106245833371143733u },
+ { 3074859046935833515u, 1684996666696914987u }, { 13527933681774397782u, 1347997333357531989u },
+ { 10576647446613305481u, 2156795733372051183u }, { 15840015586774465031u, 1725436586697640946u },
+ { 8982663654677661702u, 1380349269358112757u }, { 18061610662226169046u, 2208558830972980411u },
+ { 10759939715039024913u, 1766847064778384329u }, { 12297300586773130254u, 1413477651822707463u },
+ { 15986332124095098083u, 2261564242916331941u }, { 9099716884534168143u, 1809251394333065553u },
+ { 14658471137111155161u, 1447401115466452442u }, { 4348079280205103483u, 1157920892373161954u },
+ { 14335624477811986218u, 1852673427797059126u }, { 7779150767507678651u, 1482138742237647301u },
+ { 2533971799264232598u, 1185710993790117841u }, { 15122401323048503126u, 1897137590064188545u },
+ { 12097921058438802501u, 1517710072051350836u }, { 5988988032009131678u, 1214168057641080669u },
+ { 16961078480698431330u, 1942668892225729070u }, { 13568862784558745064u, 1554135113780583256u },
+ { 7165741412905085728u, 1243308091024466605u }, { 11465186260648137165u, 1989292945639146568u },
+ { 16550846638002330379u, 1591434356511317254u }, { 16930026125143774626u, 1273147485209053803u },
+ { 4951948911778577463u, 2037035976334486086u }, { 272210314680951647u, 1629628781067588869u },
+ { 3907117066486671641u, 1303703024854071095u }, { 6251387306378674625u, 2085924839766513752u },
+ { 16069156289328670670u, 1668739871813211001u }, { 9165976216721026213u, 1334991897450568801u },
+ { 7286864317269821294u, 2135987035920910082u }, { 16897537898041588005u, 1708789628736728065u },
+ { 13518030318433270404u, 1367031702989382452u }, { 6871453250525591353u, 2187250724783011924u },
+ { 9186511415162383406u, 1749800579826409539u }, { 11038557946871817048u, 1399840463861127631u },
+ { 10282995085511086630u, 2239744742177804210u }, { 8226396068408869304u, 1791795793742243368u },
+ { 13959814484210916090u, 1433436634993794694u }, { 11267656730511734774u, 2293498615990071511u },
+ { 5324776569667477496u, 1834798892792057209u }, { 7949170070475892320u, 1467839114233645767u },
+ { 17427382500606444826u, 1174271291386916613u }, { 5747719112518849781u, 1878834066219066582u },
+ { 15666221734240810795u, 1503067252975253265u }, { 12532977387392648636u, 1202453802380202612u },
+ { 5295368560860596524u, 1923926083808324180u }, { 4236294848688477220u, 1539140867046659344u },
+ { 7078384693692692099u, 1231312693637327475u }, { 11325415509908307358u, 1970100309819723960u },
+ { 9060332407926645887u, 1576080247855779168u }, { 14626963555825137356u, 1260864198284623334u },
+ { 12335095245094488799u, 2017382717255397335u }, { 9868076196075591040u, 1613906173804317868u },
+ { 15273158586344293478u, 1291124939043454294u }, { 13369007293925138595u, 2065799902469526871u },
+ { 7005857020398200553u, 1652639921975621497u }, { 16672732060544291412u, 1322111937580497197u },
+ { 11918976037903224966u, 2115379100128795516u }, { 5845832015580669650u, 1692303280103036413u },
+ { 12055363241948356366u, 1353842624082429130u }, { 841837113407818570u, 2166148198531886609u },
+ { 4362818505468165179u, 1732918558825509287u }, { 14558301248600263113u, 1386334847060407429u },
+ { 12225235553534690011u, 2218135755296651887u }, { 2401490813343931363u, 1774508604237321510u },
+ { 1921192650675145090u, 1419606883389857208u }, { 17831303500047873437u, 2271371013423771532u },
+ { 6886345170554478103u, 1817096810739017226u }, { 1819727321701672159u, 1453677448591213781u },
+ { 16213177116328979020u, 1162941958872971024u }, { 14873036941900635463u, 1860707134196753639u },
+ { 15587778368262418694u, 1488565707357402911u }, { 8780873879868024632u, 1190852565885922329u },
+ { 2981351763563108441u, 1905364105417475727u }, { 13453127855076217722u, 1524291284333980581u },
+ { 7073153469319063855u, 1219433027467184465u }, { 11317045550910502167u, 1951092843947495144u },
+ { 12742985255470312057u, 1560874275157996115u }, { 10194388204376249646u, 1248699420126396892u },
+ { 1553625868034358140u, 1997919072202235028u }, { 8621598323911307159u, 1598335257761788022u },
+ { 17965325103354776697u, 1278668206209430417u }, { 13987124906400001422u, 2045869129935088668u },
+ { 121653480894270168u, 1636695303948070935u }, { 97322784715416134u, 1309356243158456748u },
+ { 14913111714512307107u, 2094969989053530796u }, { 8241140556867935363u, 1675975991242824637u },
+ { 17660958889720079260u, 1340780792994259709u }, { 17189487779326395846u, 2145249268790815535u },
+ { 13751590223461116677u, 1716199415032652428u }, { 18379969808252713988u, 1372959532026121942u },
+ { 14650556434236701088u, 2196735251241795108u }, { 652398703163629901u, 1757388200993436087u },
+ { 11589965406756634890u, 1405910560794748869u }, { 7475898206584884855u, 2249456897271598191u },
+ { 2291369750525997561u, 1799565517817278553u }, { 9211793429904618695u, 1439652414253822842u },
+ { 18428218302589300235u, 2303443862806116547u }, { 7363877012587619542u, 1842755090244893238u },
+ { 13269799239553916280u, 1474204072195914590u }, { 10615839391643133024u, 1179363257756731672u },
+ { 2227947767661371545u, 1886981212410770676u }, { 16539753473096738529u, 1509584969928616540u },
+ { 13231802778477390823u, 1207667975942893232u }, { 6413489186596184024u, 1932268761508629172u },
+ { 16198837793502678189u, 1545815009206903337u }, { 5580372605318321905u, 1236652007365522670u },
+ { 8928596168509315048u, 1978643211784836272u }, { 18210923379033183008u, 1582914569427869017u },
+ { 7190041073742725760u, 1266331655542295214u }, { 436019273762630246u, 2026130648867672343u },
+ { 7727513048493924843u, 1620904519094137874u }, { 9871359253537050198u, 1296723615275310299u },
+ { 4726128361433549347u, 2074757784440496479u }, { 7470251503888749801u, 1659806227552397183u },
+ { 13354898832594820487u, 1327844982041917746u }, { 13989140502667892133u, 2124551971267068394u },
+ { 14880661216876224029u, 1699641577013654715u }, { 11904528973500979224u, 1359713261610923772u },
+ { 4289851098633925465u, 2175541218577478036u }, { 18189276137874781665u, 1740432974861982428u },
+ { 3483374466074094362u, 1392346379889585943u }, { 1884050330976640656u, 2227754207823337509u },
+ { 5196589079523222848u, 1782203366258670007u }, { 15225317707844309248u, 1425762693006936005u },
+ { 5913764258841343181u, 2281220308811097609u }, { 8420360221814984868u, 1824976247048878087u },
+ { 17804334621677718864u, 1459980997639102469u }, { 17932816512084085415u, 1167984798111281975u },
+ { 10245762345624985047u, 1868775676978051161u }, { 4507261061758077715u, 1495020541582440929u },
+ { 7295157664148372495u, 1196016433265952743u }, { 7982903447895485668u, 1913626293225524389u },
+ { 10075671573058298858u, 1530901034580419511u }, { 4371188443704728763u, 1224720827664335609u },
+ { 14372599139411386667u, 1959553324262936974u }, { 15187428126271019657u, 1567642659410349579u },
+ { 15839291315758726049u, 1254114127528279663u }, { 3206773216762499739u, 2006582604045247462u },
+ { 13633465017635730761u, 1605266083236197969u }, { 14596120828850494932u, 1284212866588958375u },
+ { 4907049252451240275u, 2054740586542333401u }, { 236290587219081897u, 1643792469233866721u },
+ { 14946427728742906810u, 1315033975387093376u }, { 16535586736504830250u, 2104054360619349402u },
+ { 5849771759720043554u, 1683243488495479522u }, { 15747863852001765813u, 1346594790796383617u },
+ { 10439186904235184007u, 2154551665274213788u }, { 15730047152871967852u, 1723641332219371030u },
+ { 12584037722297574282u, 1378913065775496824u }, { 9066413911450387881u, 2206260905240794919u },
+ { 10942479943902220628u, 1765008724192635935u }, { 8753983955121776503u, 1412006979354108748u },
+ { 10317025513452932081u, 2259211166966573997u }, { 874922781278525018u, 1807368933573259198u },
+ { 8078635854506640661u, 1445895146858607358u }, { 13841606313089133175u, 1156716117486885886u },
+ { 14767872471458792434u, 1850745787979017418u }, { 746251532941302978u, 1480596630383213935u },
+ { 597001226353042382u, 1184477304306571148u }, { 15712597221132509104u, 1895163686890513836u },
+ { 8880728962164096960u, 1516130949512411069u }, { 10793931984473187891u, 1212904759609928855u },
+ { 17270291175157100626u, 1940647615375886168u }, { 2748186495899949531u, 1552518092300708935u },
+ { 2198549196719959625u, 1242014473840567148u }, { 18275073973719576693u, 1987223158144907436u },
+ { 10930710364233751031u, 1589778526515925949u }, { 12433917106128911148u, 1271822821212740759u },
+ { 8826220925580526867u, 2034916513940385215u }, { 7060976740464421494u, 1627933211152308172u },
+ { 16716827836597268165u, 1302346568921846537u }, { 11989529279587987770u, 2083754510274954460u },
+ { 9591623423670390216u, 1667003608219963568u }, { 15051996368420132820u, 1333602886575970854u },
+ { 13015147745246481542u, 2133764618521553367u }, { 3033420566713364587u, 1707011694817242694u },
+ { 6116085268112601993u, 1365609355853794155u }, { 9785736428980163188u, 2184974969366070648u },
+ { 15207286772667951197u, 1747979975492856518u }, { 1097782973908629988u, 1398383980394285215u },
+ { 1756452758253807981u, 2237414368630856344u }, { 5094511021344956708u, 1789931494904685075u },
+ { 4075608817075965366u, 1431945195923748060u }, { 6520974107321544586u, 2291112313477996896u },
+ { 1527430471115325346u, 1832889850782397517u }, { 12289990821117991246u, 1466311880625918013u },
+ { 17210690286378213644u, 1173049504500734410u }, { 9090360384495590213u, 1876879207201175057u },
+ { 18340334751822203140u, 1501503365760940045u }, { 14672267801457762512u, 1201202692608752036u },
+ { 16096930852848599373u, 1921924308174003258u }, { 1809498238053148529u, 1537539446539202607u },
+ { 12515645034668249793u, 1230031557231362085u }, { 1578287981759648052u, 1968050491570179337u },
+ { 12330676829633449412u, 1574440393256143469u }, { 13553890278448669853u, 1259552314604914775u },
+ { 3239480371808320148u, 2015283703367863641u }, { 17348979556414297411u, 1612226962694290912u },
+ { 6500486015647617283u, 1289781570155432730u }, { 10400777625036187652u, 2063650512248692368u },
+ { 15699319729512770768u, 1650920409798953894u }, { 16248804598352126938u, 1320736327839163115u },
+ { 7551343283653851484u, 2113178124542660985u }, { 6041074626923081187u, 1690542499634128788u },
+ { 12211557331022285596u, 1352433999707303030u }, { 1091747655926105338u, 2163894399531684849u },
+ { 4562746939482794594u, 1731115519625347879u }, { 7339546366328145998u, 1384892415700278303u },
+ { 8053925371383123274u, 2215827865120445285u }, { 6443140297106498619u, 1772662292096356228u },
+ { 12533209867169019542u, 1418129833677084982u }, { 5295740528502789974u, 2269007733883335972u },
+ { 15304638867027962949u, 1815206187106668777u }, { 4865013464138549713u, 1452164949685335022u },
+ { 14960057215536570740u, 1161731959748268017u }, { 9178696285890871890u, 1858771135597228828u },
+ { 14721654658196518159u, 1487016908477783062u }, { 4398626097073393881u, 1189613526782226450u },
+ { 7037801755317430209u, 1903381642851562320u }, { 5630241404253944167u, 1522705314281249856u },
+ { 814844308661245011u, 1218164251424999885u }, { 1303750893857992017u, 1949062802279999816u },
+ { 15800395974054034906u, 1559250241823999852u }, { 5261619149759407279u, 1247400193459199882u },
+ { 12107939454356961969u, 1995840309534719811u }, { 5997002748743659252u, 1596672247627775849u },
+ { 8486951013736837725u, 1277337798102220679u }, { 2511075177753209390u, 2043740476963553087u },
+ { 13076906586428298482u, 1634992381570842469u }, { 14150874083884549109u, 1307993905256673975u },
+ { 4194654460505726958u, 2092790248410678361u }, { 18113118827372222859u, 1674232198728542688u },
+ { 3422448617672047318u, 1339385758982834151u }, { 16543964232501006678u, 2143017214372534641u },
+ { 9545822571258895019u, 1714413771498027713u }, { 15015355686490936662u, 1371531017198422170u },
+ { 5577825024675947042u, 2194449627517475473u }, { 11840957649224578280u, 1755559702013980378u },
+ { 16851463748863483271u, 1404447761611184302u }, { 12204946739213931940u, 2247116418577894884u },
+ { 13453306206113055875u, 1797693134862315907u }, { 3383947335406624054u, 1438154507889852726u },
+ { 16482362180876329456u, 2301047212623764361u }, { 9496540929959153242u, 1840837770099011489u },
+ { 11286581558709232917u, 1472670216079209191u }, { 5339916432225476010u, 1178136172863367353u },
+ { 4854517476818851293u, 1885017876581387765u }, { 3883613981455081034u, 1508014301265110212u },
+ { 14174937629389795797u, 1206411441012088169u }, { 11611853762797942306u, 1930258305619341071u },
+ { 5600134195496443521u, 1544206644495472857u }, { 15548153800622885787u, 1235365315596378285u },
+ { 6430302007287065643u, 1976584504954205257u }, { 16212288050055383484u, 1581267603963364205u },
+ { 12969830440044306787u, 1265014083170691364u }, { 9683682259845159889u, 2024022533073106183u },
+ { 15125643437359948558u, 1619218026458484946u }, { 8411165935146048523u, 1295374421166787957u },
+ { 17147214310975587960u, 2072599073866860731u }, { 10028422634038560045u, 1658079259093488585u },
+ { 8022738107230848036u, 1326463407274790868u }, { 9147032156827446534u, 2122341451639665389u },
+ { 11006974540203867551u, 1697873161311732311u }, { 5116230817421183718u, 1358298529049385849u },
+ { 15564666937357714594u, 2173277646479017358u }, { 1383687105660440706u, 1738622117183213887u },
+ { 12174996128754083534u, 1390897693746571109u }, { 8411947361780802685u, 2225436309994513775u },
+ { 6729557889424642148u, 1780349047995611020u }, { 5383646311539713719u, 1424279238396488816u },
+ { 1235136468979721303u, 2278846781434382106u }, { 15745504434151418335u, 1823077425147505684u },
+ { 16285752362063044992u, 1458461940118004547u }, { 5649904260166615347u, 1166769552094403638u },
+ { 5350498001524674232u, 1866831283351045821u }, { 591049586477829062u, 1493465026680836657u },
+ { 11540886113407994219u, 1194772021344669325u }, { 18673707743239135u, 1911635234151470921u },
+ { 14772334225162232601u, 1529308187321176736u }, { 8128518565387875758u, 1223446549856941389u },
+ { 1937583260394870242u, 1957514479771106223u }, { 8928764237799716840u, 1566011583816884978u },
+ { 14521709019723594119u, 1252809267053507982u }, { 8477339172590109297u, 2004494827285612772u },
+ { 17849917782297818407u, 1603595861828490217u }, { 6901236596354434079u, 1282876689462792174u },
+ { 18420676183650915173u, 2052602703140467478u }, { 3668494502695001169u, 1642082162512373983u },
+ { 10313493231639821582u, 1313665730009899186u }, { 9122891541139893884u, 2101865168015838698u },
+ { 14677010862395735754u, 1681492134412670958u }, { 673562245690857633u, 1345193707530136767u }
+};
+
+static const uint64_t DOUBLE_POW5_SPLIT[DOUBLE_POW5_TABLE_SIZE][2] = {
+ { 0u, 1152921504606846976u }, { 0u, 1441151880758558720u },
+ { 0u, 1801439850948198400u }, { 0u, 2251799813685248000u },
+ { 0u, 1407374883553280000u }, { 0u, 1759218604441600000u },
+ { 0u, 2199023255552000000u }, { 0u, 1374389534720000000u },
+ { 0u, 1717986918400000000u }, { 0u, 2147483648000000000u },
+ { 0u, 1342177280000000000u }, { 0u, 1677721600000000000u },
+ { 0u, 2097152000000000000u }, { 0u, 1310720000000000000u },
+ { 0u, 1638400000000000000u }, { 0u, 2048000000000000000u },
+ { 0u, 1280000000000000000u }, { 0u, 1600000000000000000u },
+ { 0u, 2000000000000000000u }, { 0u, 1250000000000000000u },
+ { 0u, 1562500000000000000u }, { 0u, 1953125000000000000u },
+ { 0u, 1220703125000000000u }, { 0u, 1525878906250000000u },
+ { 0u, 1907348632812500000u }, { 0u, 1192092895507812500u },
+ { 0u, 1490116119384765625u }, { 4611686018427387904u, 1862645149230957031u },
+ { 9799832789158199296u, 1164153218269348144u }, { 12249790986447749120u, 1455191522836685180u },
+ { 15312238733059686400u, 1818989403545856475u }, { 14528612397897220096u, 2273736754432320594u },
+ { 13692068767113150464u, 1421085471520200371u }, { 12503399940464050176u, 1776356839400250464u },
+ { 15629249925580062720u, 2220446049250313080u }, { 9768281203487539200u, 1387778780781445675u },
+ { 7598665485932036096u, 1734723475976807094u }, { 274959820560269312u, 2168404344971008868u },
+ { 9395221924704944128u, 1355252715606880542u }, { 2520655369026404352u, 1694065894508600678u },
+ { 12374191248137781248u, 2117582368135750847u }, { 14651398557727195136u, 1323488980084844279u },
+ { 13702562178731606016u, 1654361225106055349u }, { 3293144668132343808u, 2067951531382569187u },
+ { 18199116482078572544u, 1292469707114105741u }, { 8913837547316051968u, 1615587133892632177u },
+ { 15753982952572452864u, 2019483917365790221u }, { 12152082354571476992u, 1262177448353618888u },
+ { 15190102943214346240u, 1577721810442023610u }, { 9764256642163156992u, 1972152263052529513u },
+ { 17631875447420442880u, 1232595164407830945u }, { 8204786253993389888u, 1540743955509788682u },
+ { 1032610780636961552u, 1925929944387235853u }, { 2951224747111794922u, 1203706215242022408u },
+ { 3689030933889743652u, 1504632769052528010u }, { 13834660704216955373u, 1880790961315660012u },
+ { 17870034976990372916u, 1175494350822287507u }, { 17725857702810578241u, 1469367938527859384u },
+ { 3710578054803671186u, 1836709923159824231u }, { 26536550077201078u, 2295887403949780289u },
+ { 11545800389866720434u, 1434929627468612680u }, { 14432250487333400542u, 1793662034335765850u },
+ { 8816941072311974870u, 2242077542919707313u }, { 17039803216263454053u, 1401298464324817070u },
+ { 12076381983474541759u, 1751623080406021338u }, { 5872105442488401391u, 2189528850507526673u },
+ { 15199280947623720629u, 1368455531567204170u }, { 9775729147674874978u, 1710569414459005213u },
+ { 16831347453020981627u, 2138211768073756516u }, { 1296220121283337709u, 1336382355046097823u },
+ { 15455333206886335848u, 1670477943807622278u }, { 10095794471753144002u, 2088097429759527848u },
+ { 6309871544845715001u, 1305060893599704905u }, { 12499025449484531656u, 1631326116999631131u },
+ { 11012095793428276666u, 2039157646249538914u }, { 11494245889320060820u, 1274473528905961821u },
+ { 532749306367912313u, 1593091911132452277u }, { 5277622651387278295u, 1991364888915565346u },
+ { 7910200175544436838u, 1244603055572228341u }, { 14499436237857933952u, 1555753819465285426u },
+ { 8900923260467641632u, 1944692274331606783u }, { 12480606065433357876u, 1215432671457254239u },
+ { 10989071563364309441u, 1519290839321567799u }, { 9124653435777998898u, 1899113549151959749u },
+ { 8008751406574943263u, 1186945968219974843u }, { 5399253239791291175u, 1483682460274968554u },
+ { 15972438586593889776u, 1854603075343710692u }, { 759402079766405302u, 1159126922089819183u },
+ { 14784310654990170340u, 1448908652612273978u }, { 9257016281882937117u, 1811135815765342473u },
+ { 16182956370781059300u, 2263919769706678091u }, { 7808504722524468110u, 1414949856066673807u },
+ { 5148944884728197234u, 1768687320083342259u }, { 1824495087482858639u, 2210859150104177824u },
+ { 1140309429676786649u, 1381786968815111140u }, { 1425386787095983311u, 1727233711018888925u },
+ { 6393419502297367043u, 2159042138773611156u }, { 13219259225790630210u, 1349401336733506972u },
+ { 16524074032238287762u, 1686751670916883715u }, { 16043406521870471799u, 2108439588646104644u },
+ { 803757039314269066u, 1317774742903815403u }, { 14839754354425000045u, 1647218428629769253u },
+ { 4714634887749086344u, 2059023035787211567u }, { 9864175832484260821u, 1286889397367007229u },
+ { 16941905809032713930u, 1608611746708759036u }, { 2730638187581340797u, 2010764683385948796u },
+ { 10930020904093113806u, 1256727927116217997u }, { 18274212148543780162u, 1570909908895272496u },
+ { 4396021111970173586u, 1963637386119090621u }, { 5053356204195052443u, 1227273366324431638u },
+ { 15540067292098591362u, 1534091707905539547u }, { 14813398096695851299u, 1917614634881924434u },
+ { 13870059828862294966u, 1198509146801202771u }, { 12725888767650480803u, 1498136433501503464u },
+ { 15907360959563101004u, 1872670541876879330u }, { 14553786618154326031u, 1170419088673049581u },
+ { 4357175217410743827u, 1463023860841311977u }, { 10058155040190817688u, 1828779826051639971u },
+ { 7961007781811134206u, 2285974782564549964u }, { 14199001900486734687u, 1428734239102843727u },
+ { 13137066357181030455u, 1785917798878554659u }, { 11809646928048900164u, 2232397248598193324u },
+ { 16604401366885338411u, 1395248280373870827u }, { 16143815690179285109u, 1744060350467338534u },
+ { 10956397575869330579u, 2180075438084173168u }, { 6847748484918331612u, 1362547148802608230u },
+ { 17783057643002690323u, 1703183936003260287u }, { 17617136035325974999u, 2128979920004075359u },
+ { 17928239049719816230u, 1330612450002547099u }, { 17798612793722382384u, 1663265562503183874u },
+ { 13024893955298202172u, 2079081953128979843u }, { 5834715712847682405u, 1299426220705612402u },
+ { 16516766677914378815u, 1624282775882015502u }, { 11422586310538197711u, 2030353469852519378u },
+ { 11750802462513761473u, 1268970918657824611u }, { 10076817059714813937u, 1586213648322280764u },
+ { 12596021324643517422u, 1982767060402850955u }, { 5566670318688504437u, 1239229412751781847u },
+ { 2346651879933242642u, 1549036765939727309u }, { 7545000868343941206u, 1936295957424659136u },
+ { 4715625542714963254u, 1210184973390411960u }, { 5894531928393704067u, 1512731216738014950u },
+ { 16591536947346905892u, 1890914020922518687u }, { 17287239619732898039u, 1181821263076574179u },
+ { 16997363506238734644u, 1477276578845717724u }, { 2799960309088866689u, 1846595723557147156u },
+ { 10973347230035317489u, 1154122327223216972u }, { 13716684037544146861u, 1442652909029021215u },
+ { 12534169028502795672u, 1803316136286276519u }, { 11056025267201106687u, 2254145170357845649u },
+ { 18439230838069161439u, 1408840731473653530u }, { 13825666510731675991u, 1761050914342066913u },
+ { 3447025083132431277u, 2201313642927583642u }, { 6766076695385157452u, 1375821026829739776u },
+ { 8457595869231446815u, 1719776283537174720u }, { 10571994836539308519u, 2149720354421468400u },
+ { 6607496772837067824u, 1343575221513417750u }, { 17482743002901110588u, 1679469026891772187u },
+ { 17241742735199000331u, 2099336283614715234u }, { 15387775227926763111u, 1312085177259197021u },
+ { 5399660979626290177u, 1640106471573996277u }, { 11361262242960250625u, 2050133089467495346u },
+ { 11712474920277544544u, 1281333180917184591u }, { 10028907631919542777u, 1601666476146480739u },
+ { 7924448521472040567u, 2002083095183100924u }, { 14176152362774801162u, 1251301934489438077u },
+ { 3885132398186337741u, 1564127418111797597u }, { 9468101516160310080u, 1955159272639746996u },
+ { 15140935484454969608u, 1221974545399841872u }, { 479425281859160394u, 1527468181749802341u },
+ { 5210967620751338397u, 1909335227187252926u }, { 17091912818251750210u, 1193334516992033078u },
+ { 12141518985959911954u, 1491668146240041348u }, { 15176898732449889943u, 1864585182800051685u },
+ { 11791404716994875166u, 1165365739250032303u }, { 10127569877816206054u, 1456707174062540379u },
+ { 8047776328842869663u, 1820883967578175474u }, { 836348374198811271u, 2276104959472719343u },
+ { 7440246761515338900u, 1422565599670449589u }, { 13911994470321561530u, 1778206999588061986u },
+ { 8166621051047176104u, 2222758749485077483u }, { 2798295147690791113u, 1389224218428173427u },
+ { 17332926989895652603u, 1736530273035216783u }, { 17054472718942177850u, 2170662841294020979u },
+ { 8353202440125167204u, 1356664275808763112u }, { 10441503050156459005u, 1695830344760953890u },
+ { 3828506775840797949u, 2119787930951192363u }, { 86973725686804766u, 1324867456844495227u },
+ { 13943775212390669669u, 1656084321055619033u }, { 3594660960206173375u, 2070105401319523792u },
+ { 2246663100128858359u, 1293815875824702370u }, { 12031700912015848757u, 1617269844780877962u },
+ { 5816254103165035138u, 2021587305976097453u }, { 5941001823691840913u, 1263492066235060908u },
+ { 7426252279614801142u, 1579365082793826135u }, { 4671129331091113523u, 1974206353492282669u },
+ { 5225298841145639904u, 1233878970932676668u }, { 6531623551432049880u, 1542348713665845835u },
+ { 3552843420862674446u, 1927935892082307294u }, { 16055585193321335241u, 1204959932551442058u },
+ { 10846109454796893243u, 1506199915689302573u }, { 18169322836923504458u, 1882749894611628216u },
+ { 11355826773077190286u, 1176718684132267635u }, { 9583097447919099954u, 1470898355165334544u },
+ { 11978871809898874942u, 1838622943956668180u }, { 14973589762373593678u, 2298278679945835225u },
+ { 2440964573842414192u, 1436424174966147016u }, { 3051205717303017741u, 1795530218707683770u },
+ { 13037379183483547984u, 2244412773384604712u }, { 8148361989677217490u, 1402757983365377945u },
+ { 14797138505523909766u, 1753447479206722431u }, { 13884737113477499304u, 2191809349008403039u },
+ { 15595489723564518921u, 1369880843130251899u }, { 14882676136028260747u, 1712351053912814874u },
+ { 9379973133180550126u, 2140438817391018593u }, { 17391698254306313589u, 1337774260869386620u },
+ { 3292878744173340370u, 1672217826086733276u }, { 4116098430216675462u, 2090272282608416595u },
+ { 266718509671728212u, 1306420176630260372u }, { 333398137089660265u, 1633025220787825465u },
+ { 5028433689789463235u, 2041281525984781831u }, { 10060300083759496378u, 1275800953740488644u },
+ { 12575375104699370472u, 1594751192175610805u }, { 1884160825592049379u, 1993438990219513507u },
+ { 17318501580490888525u, 1245899368887195941u }, { 7813068920331446945u, 1557374211108994927u },
+ { 5154650131986920777u, 1946717763886243659u }, { 915813323278131534u, 1216698602428902287u },
+ { 14979824709379828129u, 1520873253036127858u }, { 9501408849870009354u, 1901091566295159823u },
+ { 12855909558809837702u, 1188182228934474889u }, { 2234828893230133415u, 1485227786168093612u },
+ { 2793536116537666769u, 1856534732710117015u }, { 8663489100477123587u, 1160334207943823134u },
+ { 1605989338741628675u, 1450417759929778918u }, { 11230858710281811652u, 1813022199912223647u },
+ { 9426887369424876662u, 2266277749890279559u }, { 12809333633531629769u, 1416423593681424724u },
+ { 16011667041914537212u, 1770529492101780905u }, { 6179525747111007803u, 2213161865127226132u },
+ { 13085575628799155685u, 1383226165704516332u }, { 16356969535998944606u, 1729032707130645415u },
+ { 15834525901571292854u, 2161290883913306769u }, { 2979049660840976177u, 1350806802445816731u },
+ { 17558870131333383934u, 1688508503057270913u }, { 8113529608884566205u, 2110635628821588642u },
+ { 9682642023980241782u, 1319147268013492901u }, { 16714988548402690132u, 1648934085016866126u },
+ { 11670363648648586857u, 2061167606271082658u }, { 11905663298832754689u, 1288229753919426661u },
+ { 1047021068258779650u, 1610287192399283327u }, { 15143834390605638274u, 2012858990499104158u },
+ { 4853210475701136017u, 1258036869061940099u }, { 1454827076199032118u, 1572546086327425124u },
+ { 1818533845248790147u, 1965682607909281405u }, { 3442426662494187794u, 1228551629943300878u },
+ { 13526405364972510550u, 1535689537429126097u }, { 3072948650933474476u, 1919611921786407622u },
+ { 15755650962115585259u, 1199757451116504763u }, { 15082877684217093670u, 1499696813895630954u },
+ { 9630225068416591280u, 1874621017369538693u }, { 8324733676974063502u, 1171638135855961683u },
+ { 5794231077790191473u, 1464547669819952104u }, { 7242788847237739342u, 1830684587274940130u },
+ { 18276858095901949986u, 2288355734093675162u }, { 16034722328366106645u, 1430222333808546976u },
+ { 1596658836748081690u, 1787777917260683721u }, { 6607509564362490017u, 2234722396575854651u },
+ { 1823850468512862308u, 1396701497859909157u }, { 6891499104068465790u, 1745876872324886446u },
+ { 17837745916940358045u, 2182346090406108057u }, { 4231062170446641922u, 1363966306503817536u },
+ { 5288827713058302403u, 1704957883129771920u }, { 6611034641322878003u, 2131197353912214900u },
+ { 13355268687681574560u, 1331998346195134312u }, { 16694085859601968200u, 1664997932743917890u },
+ { 11644235287647684442u, 2081247415929897363u }, { 4971804045566108824u, 1300779634956185852u },
+ { 6214755056957636030u, 1625974543695232315u }, { 3156757802769657134u, 2032468179619040394u },
+ { 6584659645158423613u, 1270292612261900246u }, { 17454196593302805324u, 1587865765327375307u },
+ { 17206059723201118751u, 1984832206659219134u }, { 6142101308573311315u, 1240520129162011959u },
+ { 3065940617289251240u, 1550650161452514949u }, { 8444111790038951954u, 1938312701815643686u },
+ { 665883850346957067u, 1211445438634777304u }, { 832354812933696334u, 1514306798293471630u },
+ { 10263815553021896226u, 1892883497866839537u }, { 17944099766707154901u, 1183052186166774710u },
+ { 13206752671529167818u, 1478815232708468388u }, { 16508440839411459773u, 1848519040885585485u },
+ { 12623618533845856310u, 1155324400553490928u }, { 15779523167307320387u, 1444155500691863660u },
+ { 1277659885424598868u, 1805194375864829576u }, { 1597074856780748586u, 2256492969831036970u },
+ { 5609857803915355770u, 1410308106144398106u }, { 16235694291748970521u, 1762885132680497632u },
+ { 1847873790976661535u, 2203606415850622041u }, { 12684136165428883219u, 1377254009906638775u },
+ { 11243484188358716120u, 1721567512383298469u }, { 219297180166231438u, 2151959390479123087u },
+ { 7054589765244976505u, 1344974619049451929u }, { 13429923224983608535u, 1681218273811814911u },
+ { 12175718012802122765u, 2101522842264768639u }, { 14527352785642408584u, 1313451776415480399u },
+ { 13547504963625622826u, 1641814720519350499u }, { 12322695186104640628u, 2052268400649188124u },
+ { 16925056528170176201u, 1282667750405742577u }, { 7321262604930556539u, 1603334688007178222u },
+ { 18374950293017971482u, 2004168360008972777u }, { 4566814905495150320u, 1252605225005607986u },
+ { 14931890668723713708u, 1565756531257009982u }, { 9441491299049866327u, 1957195664071262478u },
+ { 1289246043478778550u, 1223247290044539049u }, { 6223243572775861092u, 1529059112555673811u },
+ { 3167368447542438461u, 1911323890694592264u }, { 1979605279714024038u, 1194577431684120165u },
+ { 7086192618069917952u, 1493221789605150206u }, { 18081112809442173248u, 1866527237006437757u },
+ { 13606538515115052232u, 1166579523129023598u }, { 7784801107039039482u, 1458224403911279498u },
+ { 507629346944023544u, 1822780504889099373u }, { 5246222702107417334u, 2278475631111374216u },
+ { 3278889188817135834u, 1424047269444608885u }, { 8710297504448807696u, 1780059086805761106u }
+};
+
+// IEEE 754 double precision constants
+#define DOUBLE_MANTISSA_BITS 52
+#define DOUBLE_EXPONENT_BITS 11
+#define DOUBLE_EXPONENT_BIAS 1023
+
+// Helper: floor(log2(value)) using ryu_leading_zeros64
+static inline uint32_t floor_log2(const uint64_t value) {
+ return 63 - ryu_leading_zeros64(value);
+}
+
+// Helper: log2(5^e) approximation
+static inline int32_t log2pow5(const int32_t e) {
+ return (int32_t) ((((uint32_t) e) * 1217359) >> 19);
+}
+
+// Helper: ceil(log2(5^e))
+static inline int32_t ceil_log2pow5(const int32_t e) {
+ return log2pow5(e) + 1;
+}
+
+// Helper: max of two int32
+static inline int32_t max32(int32_t a, int32_t b) {
+ return a < b ? b : a;
+}
+
+// Helper: convert uint64 bits to double
+static inline double int64Bits2Double(uint64_t bits) {
+ double f;
+ memcpy(&f, &bits, sizeof(double));
+ return f;
+}
+
+// Check if value is multiple of 2^p
+static inline bool multipleOfPowerOf2(const uint64_t value, const uint32_t p) {
+ return (value & ((1ull << p) - 1)) == 0;
+}
+
+// Count how many times value is divisible by 5
+// Uses modular inverse to avoid expensive division
+static inline uint32_t pow5Factor(uint64_t value) {
+ const uint64_t m_inv_5 = 14757395258967641293u; // 5 * m_inv_5 = 1 (mod 2^64)
+ const uint64_t n_div_5 = 3689348814741910323u; // 2^64 / 5
+ uint32_t count = 0;
+ for (;;) {
+ value *= m_inv_5;
+ if (value > n_div_5)
+ break;
+ ++count;
+ }
+ return count;
+}
+
+// Check if value is multiple of 5^p
+// Optimized: uses modular inverse instead of division
+static inline bool multipleOfPowerOf5(const uint64_t value, const uint32_t p) {
+ return pow5Factor(value) >= p;
+}
+
+// 128-bit multiplication with shift
+// This is the core operation for converting decimal to binary
+#if defined(__SIZEOF_INT128__)
+// Use native 128-bit integers if available (GCC/Clang)
+static inline uint64_t mulShift64(const uint64_t m, const uint64_t* const mul, const int32_t j) {
+ const unsigned __int128 b0 = ((unsigned __int128) m) * mul[0];
+ const unsigned __int128 b2 = ((unsigned __int128) m) * mul[1];
+ return (uint64_t) (((b0 >> 64) + b2) >> (j - 64));
+}
+#else
+// Fallback for systems without 128-bit integers
+static inline uint64_t umul128(const uint64_t a, const uint64_t b, uint64_t* const productHi) {
+ const uint32_t aLo = (uint32_t)a;
+ const uint32_t aHi = (uint32_t)(a >> 32);
+ const uint32_t bLo = (uint32_t)b;
+ const uint32_t bHi = (uint32_t)(b >> 32);
+
+ const uint64_t b00 = (uint64_t)aLo * bLo;
+ const uint64_t b01 = (uint64_t)aLo * bHi;
+ const uint64_t b10 = (uint64_t)aHi * bLo;
+ const uint64_t b11 = (uint64_t)aHi * bHi;
+
+ const uint32_t b00Lo = (uint32_t)b00;
+ const uint32_t b00Hi = (uint32_t)(b00 >> 32);
+
+ const uint64_t mid1 = b10 + b00Hi;
+ const uint32_t mid1Lo = (uint32_t)(mid1);
+ const uint32_t mid1Hi = (uint32_t)(mid1 >> 32);
+
+ const uint64_t mid2 = b01 + mid1Lo;
+ const uint32_t mid2Lo = (uint32_t)(mid2);
+ const uint32_t mid2Hi = (uint32_t)(mid2 >> 32);
+
+ const uint64_t pHi = b11 + mid1Hi + mid2Hi;
+ const uint64_t pLo = ((uint64_t)mid2Lo << 32) | b00Lo;
+
+ *productHi = pHi;
+ return pLo;
+}
+
+static inline uint64_t shiftright128(const uint64_t lo, const uint64_t hi, const uint32_t dist) {
+ return (hi << (64 - dist)) | (lo >> dist);
+}
+
+static inline uint64_t mulShift64(const uint64_t m, const uint64_t* const mul, const int32_t j) {
+ uint64_t high1;
+ const uint64_t low1 = umul128(m, mul[1], &high1);
+ uint64_t high0;
+ umul128(m, mul[0], &high0);
+ const uint64_t sum = high0 + low1;
+ if (sum < high0) {
+ ++high1;
+ }
+ return shiftright128(sum, high1, j - 64);
+}
+#endif
+
+// Main conversion function: decimal mantissa+exponent to IEEE 754 double
+// Optimized for JSON parsing with fast paths for edge cases
+static inline double ryu_s2d_from_parts(uint64_t m10, int m10digits, int32_t e10, bool signedM) {
+ // Fast path: handle zero explicitly (e.g., "0.0", "0e0")
+ if (m10 == 0) {
+ return int64Bits2Double(((uint64_t) signedM) << 63);
+ }
+
+ // Fast path: handle overflow/underflow early
+ if (m10digits + e10 <= -324) {
+ // Underflow to zero
+ return int64Bits2Double(((uint64_t) signedM) << 63);
+ }
+
+ if (m10digits + e10 >= 310) {
+ // Overflow to infinity
+ return int64Bits2Double((((uint64_t) signedM) << 63) | 0x7ff0000000000000ULL);
+ }
+
+ // Convert decimal to binary: m10 * 10^e10 = m2 * 2^e2
+ int32_t e2;
+ uint64_t m2;
+ bool trailingZeros;
+
+ if (e10 >= 0) {
+ // Positive exponent: multiply by 5^e10 and adjust binary exponent
+ e2 = floor_log2(m10) + e10 + log2pow5(e10) - (DOUBLE_MANTISSA_BITS + 1);
+ int j = e2 - e10 - ceil_log2pow5(e10) + DOUBLE_POW5_BITCOUNT;
+ m2 = mulShift64(m10, DOUBLE_POW5_SPLIT[e10], j);
+ trailingZeros = e2 < e10 || (e2 - e10 < 64 && multipleOfPowerOf2(m10, e2 - e10));
+ } else {
+ // Negative exponent: divide by 5^(-e10)
+ e2 = floor_log2(m10) + e10 - ceil_log2pow5(-e10) - (DOUBLE_MANTISSA_BITS + 1);
+ int j = e2 - e10 + ceil_log2pow5(-e10) - 1 + DOUBLE_POW5_INV_BITCOUNT;
+ m2 = mulShift64(m10, DOUBLE_POW5_INV_SPLIT[-e10], j);
+ trailingZeros = multipleOfPowerOf5(m10, -e10);
+ }
+
+ // Compute IEEE 754 exponent
+ uint32_t ieee_e2 = (uint32_t) max32(0, e2 + DOUBLE_EXPONENT_BIAS + floor_log2(m2));
+
+ if (ieee_e2 > 0x7fe) {
+ // Overflow to infinity
+ return int64Bits2Double((((uint64_t) signedM) << 63) | 0x7ff0000000000000ULL);
+ }
+
+ // Compute shift amount for rounding
+ int32_t shift = (ieee_e2 == 0 ? 1 : ieee_e2) - e2 - DOUBLE_EXPONENT_BIAS - DOUBLE_MANTISSA_BITS;
+
+ // IEEE 754 round-to-even (banker's rounding)
+ trailingZeros &= (m2 & ((1ull << (shift - 1)) - 1)) == 0;
+ uint64_t lastRemovedBit = (m2 >> (shift - 1)) & 1;
+ bool roundUp = (lastRemovedBit != 0) && (!trailingZeros || (((m2 >> shift) & 1) != 0));
+
+ uint64_t ieee_m2 = (m2 >> shift) + roundUp;
+ ieee_m2 &= (1ull << DOUBLE_MANTISSA_BITS) - 1;
+
+ if (ieee_m2 == 0 && roundUp) {
+ ieee_e2++;
+ }
+
+ // Pack sign, exponent, and mantissa into IEEE 754 format
+ // Match original Ryu: group sign+exponent, then shift and add mantissa
+ uint64_t ieee = (((((uint64_t) signedM) << DOUBLE_EXPONENT_BITS) | (uint64_t)ieee_e2) << DOUBLE_MANTISSA_BITS) | ieee_m2;
+ return int64Bits2Double(ieee);
+}
+
+#endif // RYU_H