Skip to content

Instantly share code, notes, and snippets.

@headius
Created October 17, 2024 21:46
Show Gist options
  • Save headius/40b69293f3d34a9bfc1373675369dba8 to your computer and use it in GitHub Desktop.
Save headius/40b69293f3d34a9bfc1373675369dba8 to your computer and use it in GitHub Desktop.
proof of concept removing C parts of Json::Generator::State from Ruby json gem
diff --git a/java/src/json/ext/GeneratorState.java b/java/src/json/ext/GeneratorState.java
index 909f1a5..e3000b8 100644
--- a/java/src/json/ext/GeneratorState.java
+++ b/java/src/json/ext/GeneratorState.java
@@ -158,41 +158,6 @@ public class GeneratorState extends RubyObject {
return (GeneratorState)info.getSafeStatePrototype(context).dup();
}
- /**
- * <code>State#initialize(opts = {})</code>
- *
- * Instantiates a new <code>State</code> object, configured by <code>opts</code>.
- *
- * <code>opts</code> can have the following keys:
- *
- * <dl>
- * <dt><code>:indent</code>
- * <dd>a {@link RubyString String} used to indent levels (default: <code>""</code>)
- * <dt><code>:space</code>
- * <dd>a String that is put after a <code>':'</code> or <code>','</code>
- * delimiter (default: <code>""</code>)
- * <dt><code>:space_before</code>
- * <dd>a String that is put before a <code>":"</code> pair delimiter
- * (default: <code>""</code>)
- * <dt><code>:object_nl</code>
- * <dd>a String that is put at the end of a JSON object (default: <code>""</code>)
- * <dt><code>:array_nl</code>
- * <dd>a String that is put at the end of a JSON array (default: <code>""</code>)
- * <dt><code>:allow_nan</code>
- * <dd><code>true</code> if <code>NaN</code>, <code>Infinity</code>, and
- * <code>-Infinity</code> should be generated, otherwise an exception is
- * thrown if these values are encountered.
- * This options defaults to <code>false</code>.
- * <dt><code>:script_safe</code>
- * <dd>set to <code>true</code> if U+2028, U+2029 and forward slashes should be escaped
- * in the json output to make it safe to include in a JavaScript tag (default: <code>false</code>)
- */
- @JRubyMethod(optional=1, visibility=Visibility.PRIVATE)
- public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
- configure(context, args.length > 0 ? args[0] : null);
- return this;
- }
-
@JRubyMethod
public IRubyObject initialize_copy(ThreadContext context, IRubyObject vOrig) {
Ruby runtime = context.getRuntime();
@@ -239,29 +204,6 @@ public class GeneratorState extends RubyObject {
return false;
}
- @JRubyMethod(name="[]", required=1)
- public IRubyObject op_aref(ThreadContext context, IRubyObject vName) {
- String name = vName.asJavaString();
- if (getMetaClass().isMethodBound(name, true)) {
- return send(context, vName, Block.NULL_BLOCK);
- } else {
- IRubyObject value = getInstanceVariables().getInstanceVariable("@" + name);
- return value == null ? context.nil : value;
- }
- }
-
- @JRubyMethod(name="[]=", required=2)
- public IRubyObject op_aset(ThreadContext context, IRubyObject vName, IRubyObject value) {
- String name = vName.asJavaString();
- String nameWriter = name + "=";
- if (getMetaClass().isMethodBound(nameWriter, true)) {
- return send(context, context.getRuntime().newString(nameWriter), value, Block.NULL_BLOCK);
- } else {
- getInstanceVariables().setInstanceVariable("@" + name, value);
- }
- return context.getRuntime().getNil();
- }
-
public ByteList getIndent() {
return indent;
}
@@ -458,78 +400,6 @@ public class GeneratorState extends RubyObject {
return str.getByteList().dup();
}
- /**
- * <code>State#configure(opts)</code>
- *
- * <p>Configures this State instance with the {@link RubyHash Hash}
- * <code>opts</code>, and returns itself.
- * @param vOpts The options hash
- * @return The receiver
- */
- @JRubyMethod(alias = "merge")
- public IRubyObject configure(ThreadContext context, IRubyObject vOpts) {
- OptionsReader opts = new OptionsReader(context, vOpts);
-
- ByteList indent = opts.getString("indent");
- if (indent != null) this.indent = indent;
-
- ByteList space = opts.getString("space");
- if (space != null) this.space = space;
-
- ByteList spaceBefore = opts.getString("space_before");
- if (spaceBefore != null) this.spaceBefore = spaceBefore;
-
- ByteList arrayNl = opts.getString("array_nl");
- if (arrayNl != null) this.arrayNl = arrayNl;
-
- ByteList objectNl = opts.getString("object_nl");
- if (objectNl != null) this.objectNl = objectNl;
-
- maxNesting = opts.getInt("max_nesting", DEFAULT_MAX_NESTING);
- allowNaN = opts.getBool("allow_nan", DEFAULT_ALLOW_NAN);
- asciiOnly = opts.getBool("ascii_only", DEFAULT_ASCII_ONLY);
- scriptSafe = opts.getBool("script_safe", DEFAULT_SCRIPT_SAFE);
- if (!scriptSafe) {
- scriptSafe = opts.getBool("escape_slash", DEFAULT_SCRIPT_SAFE);
- }
- strict = opts.getBool("strict", DEFAULT_STRICT);
- bufferInitialLength = opts.getInt("buffer_initial_length", DEFAULT_BUFFER_INITIAL_LENGTH);
-
- depth = opts.getInt("depth", 0);
-
- return this;
- }
-
- /**
- * <code>State#to_h()</code>
- *
- * <p>Returns the configuration instance variables as a hash, that can be
- * passed to the configure method.
- * @return the hash
- */
- @JRubyMethod(alias = "to_hash")
- public RubyHash to_h(ThreadContext context) {
- Ruby runtime = context.getRuntime();
- RubyHash result = RubyHash.newHash(runtime);
-
- result.op_aset(context, runtime.newSymbol("indent"), indent_get(context));
- result.op_aset(context, runtime.newSymbol("space"), space_get(context));
- result.op_aset(context, runtime.newSymbol("space_before"), space_before_get(context));
- result.op_aset(context, runtime.newSymbol("object_nl"), object_nl_get(context));
- result.op_aset(context, runtime.newSymbol("array_nl"), array_nl_get(context));
- result.op_aset(context, runtime.newSymbol("allow_nan"), allow_nan_p(context));
- result.op_aset(context, runtime.newSymbol("ascii_only"), ascii_only_p(context));
- result.op_aset(context, runtime.newSymbol("max_nesting"), max_nesting_get(context));
- result.op_aset(context, runtime.newSymbol("script_safe"), script_safe_get(context));
- result.op_aset(context, runtime.newSymbol("strict"), strict_get(context));
- result.op_aset(context, runtime.newSymbol("depth"), depth_get(context));
- result.op_aset(context, runtime.newSymbol("buffer_initial_length"), buffer_initial_length_get(context));
- for (String name: getInstanceVariableNameList()) {
- result.op_aset(context, runtime.newSymbol(name.substring(1)), getInstanceVariables().getInstanceVariable(name));
- }
- return result;
- }
-
public int increaseDepth() {
depth++;
checkMaxNesting();
diff --git a/lib/json/pure/generator.rb b/lib/json/pure/generator.rb
index 75b0a40..4a8edca 100644
--- a/lib/json/pure/generator.rb
+++ b/lib/json/pure/generator.rb
@@ -114,7 +114,8 @@ module JSON
SAFE_STATE_PROTOTYPE.dup
end
end
-
+ # call-seq: new(opts = {})
+ #
# Instantiates a new State object, configured by _opts_.
#
# _opts_ can have the following keys:
@@ -124,26 +125,17 @@ module JSON
# * *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: ''),
- # * *script_safe*: true if U+2028, U+2029 and forward slash (/) should be escaped
- # as to make the JSON object safe to interpolate in a script tag (default: false).
- # * *check_circular*: is deprecated now, use the :max_nesting option instead,
- # * *max_nesting*: sets the maximum level of data structure nesting in
- # the generated JSON, max_nesting = 0 if no maximum should be checked.
# * *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 = {})
- @indent = ''
- @space = ''
- @space_before = ''
- @object_nl = ''
- @array_nl = ''
- @allow_nan = false
- @ascii_only = false
- @script_safe = false
- @strict = false
- @buffer_initial_length = 1024
- configure opts
+ # * *ascii_only*: true if only ASCII characters should be generated. This
+ # option defaults to false.
+ # * *buffer_initial_length*: sets the initial length of the generator's
+ # internal buffer.
+ def initialize(opts = nil)
+ if opts && !opts.empty?
+ configure(opts)
+ end
end
# This string is used to indent levels in the JSON text.
@@ -226,64 +218,84 @@ module JSON
@strict
end
+ # call-seq: configure(opts)
+ #
# Configure this State instance with the Hash _opts_, and return
# itself.
def configure(opts)
- 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
- opts.each do |key, value|
- instance_variable_set "@#{key}", value
+ 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
- # NOTE: If adding new instance variables here, check whether #generate should check them for #generate_json
- @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)
- @allow_nan = !!opts[:allow_nan] if opts.key?(:allow_nan)
- @ascii_only = opts[:ascii_only] if opts.key?(:ascii_only)
- @depth = opts[:depth] || 0
- @buffer_initial_length ||= opts[:buffer_initial_length]
-
- @script_safe = if opts.key?(:script_safe)
- !!opts[:script_safe]
- elsif opts.key?(:escape_slash)
- !!opts[:escape_slash]
- else
- false
+ opts.each do |key, value|
+ case key
+ when :indent
+ self.indent = value
+ when :space
+ self.space = value
+ when :space_before
+ self.space_before = value
+ when :array_nl
+ self.array_nl = value
+ when :object_nl
+ self.object_nl = value
+ when :max_nesting
+ self.max_nesting = value || 0
+ when :depth
+ self.depth = value
+ when :buffer_initial_length
+ self.buffer_initial_length = value
+ when :allow_nan
+ self.allow_nan = value
+ when :ascii_only
+ self.ascii_only = value
+ when :script_safe, :escape_slash
+ self.script_safe = value
+ when :strict
+ self.strict = value
+ end
end
- @strict = !!opts[:strict] if opts.key?(:strict)
-
- if !opts.key?(:max_nesting) # defaults to 100
- @max_nesting = 100
- elsif opts[:max_nesting]
- @max_nesting = opts[:max_nesting]
- else
- @max_nesting = 0
- end
self
end
- alias merge configure
+ 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 = {}
+ result = {
+ indent: indent,
+ space: space,
+ space_before: space_before,
+ object_nl: object_nl,
+ array_nl: array_nl,
+ 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,
+ }
+
instance_variables.each do |iv|
iv = iv.to_s[1..-1]
result[iv.to_sym] = self[iv]
end
+
result
end
- alias to_hash to_h
+ alias_method :to_hash, :to_h
# Generates a valid JSON document from object +obj+ and
# returns the result. If no valid JSON document can be
@@ -354,7 +366,9 @@ module JSON
end
end
- # Return the value returned by method +name+.
+ # call-seq: [](name)
+ #
+ # Returns the value returned by method +name+.
def [](name)
if respond_to?(name)
__send__(name)
@@ -364,6 +378,9 @@ module JSON
end
end
+ # call-seq: []=(name, value)
+ #
+ # Sets the attribute name to value.
def []=(name, value)
if respond_to?(name_writer = "#{name}=")
__send__ name_writer, value
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment