// line 1 "Parser.rl"
/*
* This code is copyrighted work by Daniel Luz <dev at mernen dot com>.
*
* Distributed under the Ruby and GPLv2 licenses; see COPYING and GPL files
* for details.
*/
package json.ext;
import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyClass;
import org.jruby.RubyEncoding;
import org.jruby.RubyFloat;
import org.jruby.RubyHash;
import org.jruby.RubyInteger;
import org.jruby.RubyModule;
import org.jruby.RubyNumeric;
import org.jruby.RubyObject;
import org.jruby.RubyString;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.JumpException;
import org.jruby.exceptions.RaiseException;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;
import org.jruby.util.ConvertBytes;
import static org.jruby.util.ConvertDouble.DoubleConverter;
/**
* The <code>JSON::Ext::Parser</code> class.
*
* <p>This is the JSON parser implemented as a Java class. To use it as the
* standard parser, set
* <pre>JSON.parser = JSON::Ext::Parser</pre>
* This is performed for you when you <code>include "json/ext"</code>.
*
* <p>This class does not perform the actual parsing, just acts as an interface
* to Ruby code. When the {@link #parse()} method is invoked, a
* Parser.ParserSession object is instantiated, which handles the process.
*
* @author mernen
*/
public class Parser extends RubyObject {
private final RuntimeInfo info;
private RubyString vSource;
private RubyString createId;
private boolean createAdditions;
private int maxNesting;
private boolean allowNaN;
private boolean symbolizeNames;
private boolean quirksMode;
private RubyClass objectClass;
private RubyClass arrayClass;
private RubyHash match_string;
private static final int DEFAULT_MAX_NESTING = 19;
private static final ByteList JSON_MINUS_INFINITY = new ByteList(ByteList.plain("-Infinity"));
// constant names in the JSON module containing those values
private static final String CONST_NAN = "NaN";
private static final String CONST_INFINITY = "Infinity";
private static final String CONST_MINUS_INFINITY = "MinusInfinity";
static final ObjectAllocator ALLOCATOR = new ObjectAllocator() {
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
return new Parser(runtime, klazz);
}
};
/**
* Multiple-value return for internal parser methods.
*
* <p>All the <code>parse<var>Stuff</var></code> methods return instances of
* <code>ParserResult</code> when successful, or <code>null</code> when
* there's a problem with the input data.
*/
static final class ParserResult {
/**
* The result of the successful parsing. Should never be
* <code>null</code>.
*/
IRubyObject result;
/**
* The point where the parser returned.
*/
int p;
void update(IRubyObject result, int p) {
this.result = result;
this.p = p;
}
}
public Parser(Ruby runtime, RubyClass metaClass) {
super(runtime, metaClass);
info = RuntimeInfo.forRuntime(runtime);
}
/**
* <code>Parser.new(source, opts = {})</code>
*
* <p>Creates a new <code>JSON::Ext::Parser</code> instance for the string
* <code>source</code>.
* It will be configured by the <code>opts</code> Hash.
* <code>opts</code> can have the following keys:
*
* <dl>
* <dt><code>:max_nesting</code>
* <dd>The maximum depth of nesting allowed in the parsed data
* structures. Disable depth checking with <code>:max_nesting => false|nil|0</code>,
* it defaults to 19.
*
* <dt><code>:allow_nan</code>
* <dd>If set to <code>true</code>, allow <code>NaN</code>,
* <code>Infinity</code> and <code>-Infinity</code> in defiance of RFC 4627
* to be parsed by the Parser. This option defaults to <code>false</code>.
*
* <dt><code>:symbolize_names</code>
* <dd>If set to <code>true</code>, returns symbols for the names (keys) in
* a JSON object. Otherwise strings are returned, which is also the default.
*
* <dt><code>:quirks_mode?</code>
* <dd>If set to <code>true</code>, if the parse is in quirks_mode, false
* otherwise.
*
* <dt><code>:create_additions</code>
* <dd>If set to <code>false</code>, the Parser doesn't create additions
* even if a matchin class and <code>create_id</code> was found. This option
* defaults to <code>true</code>.
*
* <dt><code>:object_class</code>
* <dd>Defaults to Hash.
*
* <dt><code>:array_class</code>
* <dd>Defaults to Array.
*
* <dt><code>:quirks_mode</code>
* <dd>Enables quirks_mode for parser, that is for example parsing single
* JSON values instead of documents is possible.
* </dl>
*/
@JRubyMethod(name = "new", required = 1, optional = 1, meta = true)
public static IRubyObject newInstance(IRubyObject clazz, IRubyObject[] args, Block block) {
Parser parser = (Parser)((RubyClass)clazz).allocate();
parser.callInit(args, block);
return parser;
}
@JRubyMethod(required = 1, optional = 1, visibility = Visibility.PRIVATE)
public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
Ruby runtime = context.getRuntime();
if (this.vSource != null) {
throw runtime.newTypeError("already initialized instance");
}
OptionsReader opts = new OptionsReader(context, args.length > 1 ? args[1] : null);
this.maxNesting = opts.getInt("max_nesting", DEFAULT_MAX_NESTING);
this.allowNaN = opts.getBool("allow_nan", false);
this.symbolizeNames = opts.getBool("symbolize_names", false);
this.quirksMode = opts.getBool("quirks_mode", false);
this.createId = opts.getString("create_id", getCreateId(context));
this.createAdditions = opts.getBool("create_additions", true);
this.objectClass = opts.getClass("object_class", runtime.getHash());
this.arrayClass = opts.getClass("array_class", runtime.getArray());
this.match_string = opts.getHash("match_string");
this.vSource = args[0].convertToString();
if (!quirksMode) this.vSource = convertEncoding(context, vSource);
return this;
}
/**
* Checks the given string's encoding. If a non-UTF-8 encoding is detected,
* a converted copy is returned.
* Returns the source string if no conversion is needed.
*/
private RubyString convertEncoding(ThreadContext context, RubyString source) {
ByteList bl = source.getByteList();
int len = bl.length();
if (len < 2) {
throw Utils.newException(context, Utils.M_PARSER_ERROR,
"A JSON text must at least contain two octets!");
}
if (info.encodingsSupported()) {
RubyEncoding encoding = (RubyEncoding)source.encoding(context);
if (encoding != info.ascii8bit.get()) {
return (RubyString)source.encode(context, info.utf8.get());
}
String sniffedEncoding = sniffByteList(bl);
if (sniffedEncoding == null) return source; // assume UTF-8
return reinterpretEncoding(context, source, sniffedEncoding);
}
String sniffedEncoding = sniffByteList(bl);
if (sniffedEncoding == null) return source; // assume UTF-8
Ruby runtime = context.getRuntime();
return (RubyString)info.jsonModule.get().
callMethod(context, "iconv",
new IRubyObject[] {
runtime.newString("utf-8"),
runtime.newString(sniffedEncoding),
评论0