diff --git a/Rakefile b/Rakefile index d05b45b7..c11d7b8d 100644 --- a/Rakefile +++ b/Rakefile @@ -84,8 +84,6 @@ namespace :ci do end JAVA_DIR = "java/src/json/ext" -JAVA_RAGEL_PATH = "#{JAVA_DIR}/ParserConfig.rl" -JAVA_PARSER_SRC = "#{JAVA_DIR}/ParserConfig.java" JAVA_SOURCES = FileList["#{JAVA_DIR}/*.java"].exclude("#{JAVA_DIR}/Vectorized*.java") JAVA_VEC_SOURCES = FileList["#{JAVA_DIR}/Vectorized*.java"] JAVA_CLASSES = [] @@ -96,35 +94,6 @@ CLEAN.concat FileList["java/src/**/*.class"] CLEAN << JRUBY_PARSER_JAR CLEAN << JRUBY_GENERATOR_JAR -CLOBBER << JAVA_PARSER_SRC - -which = lambda { |c| - w = `which #{c}` - break w.chomp unless w.empty? -} - -if RUBY_PLATFORM =~ /mingw|mswin/ - # cleans up Windows CI output - RAGEL_CODEGEN = %w[ragel].find(&which) - RAGEL_DOTGEN = %w[ragel].find(&which) -else - RAGEL_CODEGEN = %w[rlcodegen rlgen-cd ragel].find(&which) - RAGEL_DOTGEN = %w[rlgen-dot rlgen-cd ragel].find(&which) -end - -file JAVA_PARSER_SRC => JAVA_RAGEL_PATH do - cd JAVA_DIR do - if RAGEL_CODEGEN == 'ragel' - sh "ragel ParserConfig.rl -J -o ParserConfig.java" - else - sh "ragel -x ParserConfig.rl | #{RAGEL_CODEGEN} -J" - end - end -end - -desc "Generate parser with ragel" -task :ragel => [JAVA_PARSER_SRC] - if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'jruby' path_separator = File::PATH_SEPARATOR ENV['JAVA_HOME'] ||= [ @@ -174,7 +143,7 @@ if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'jruby' end desc "Compiling jruby extension" - task :compile => [:ragel] + JAVA_CLASSES + task :compile => JAVA_CLASSES desc "Package the jruby gem" task :jruby_gem => :create_jar do @@ -197,8 +166,10 @@ if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'jruby' "json/ext/ByteList*.class", "json/ext/OptionsReader*.class", "json/ext/Parser*.class", + "json/ext/Ryu*.class", "json/ext/RuntimeInfo*.class", "json/ext/StringDecoder*.class", + "json/ext/*StringScanner*.class", "json/ext/Utils*.class" ] sh 'jar', 'cf', File.basename(JRUBY_PARSER_JAR), *parser_classes diff --git a/java/src/json/ext/Parser.java b/java/src/json/ext/Parser.java new file mode 100644 index 00000000..c54d1a7f --- /dev/null +++ b/java/src/json/ext/Parser.java @@ -0,0 +1,935 @@ +package json.ext; + +import org.jcodings.Encoding; +import org.jcodings.specific.ASCIIEncoding; +import org.jcodings.specific.UTF8Encoding; +import org.jruby.Ruby; +import org.jruby.RubyArray; +import org.jruby.RubyClass; +import org.jruby.RubyFloat; +import org.jruby.RubyHash; +import org.jruby.RubyObject; +import org.jruby.RubyProc; +import org.jruby.RubyString; +import org.jruby.RubySymbol; +import org.jruby.anno.JRubyMethod; +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 org.jruby.util.ConvertDouble.DoubleConverter; +import org.jruby.util.ConvertBytes; +import org.jruby.util.ConvertDouble.DoubleConverter; +import org.jruby.util.ConvertBytes; +import org.jruby.util.ConvertDouble.DoubleConverter; +import org.jruby.util.ConvertBytes; +import org.jruby.util.ConvertDouble.DoubleConverter; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.function.BiFunction; + +import static json.ext.Ryu.ryuS2dFromParts; +import static org.jruby.util.ConvertDouble.DoubleConverter; + +/** + * This is a port of the parser from the C extension. + */ +public class Parser extends RubyObject { + private final RuntimeInfo info; + private int maxNesting; + private boolean allowNaN; + private boolean allowTrailingComma; + private boolean allowComments; + private boolean deprecateComments; + private boolean allowControlCharacters; + private boolean allowInvalidEscape; + private boolean allowDuplicateKey; + private boolean deprecateDuplicateKey; + private boolean symbolizeNames; + private boolean freeze; + private RubyProc onLoadProc; + private RubyClass decimalClass; + BiFunction decimalFactory; + + private static final int DEFAULT_MAX_NESTING = 100; + + // Maximum number of deprecation warnings emitted per parse, to avoid + // flooding output on pathological inputs. + private static final int MAX_DEPRECATIONS = 5; + + // 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 = Parser::new; + + public Parser(Ruby runtime, RubyClass metaClass) { + super(runtime, metaClass); + info = RuntimeInfo.forRuntime(runtime); + } + + @JRubyMethod(name = "new", meta = true) + public static IRubyObject newInstance(IRubyObject clazz, IRubyObject arg0, Block block) { + Parser config = (Parser)((RubyClass)clazz).allocate(); + config.callInit(arg0, block); + return config; + } + + @JRubyMethod(name = "new", meta = true) + public static IRubyObject newInstance(IRubyObject clazz, IRubyObject arg0, IRubyObject arg1, Block block) { + Parser config = (Parser)((RubyClass)clazz).allocate(); + config.callInit(arg0, arg1, block); + return config; + } + + @JRubyMethod(visibility = Visibility.PRIVATE) + public IRubyObject initialize(ThreadContext context, IRubyObject options) { + checkFrozen(); + Ruby runtime = context.runtime; + + OptionsReader opts = new OptionsReader(context, options); + this.maxNesting = opts.getInt("max_nesting", DEFAULT_MAX_NESTING); + this.allowNaN = opts.getBool("allow_nan", false); + if (opts.hasKey("allow_comments")) { + this.allowComments = opts.getBool("allow_comments", false); + this.deprecateComments = false; + } else { + this.allowComments = true; + this.deprecateComments = true; + } + + this.allowControlCharacters = opts.getBool("allow_control_characters", false); + this.allowInvalidEscape = opts.getBool("allow_invalid_escape", false); + this.allowTrailingComma = opts.getBool("allow_trailing_comma", false); + this.symbolizeNames = opts.getBool("symbolize_names", false); + if (opts.hasKey("allow_duplicate_key")) { + this.allowDuplicateKey = opts.getBool("allow_duplicate_key", false); + this.deprecateDuplicateKey = false; + } else { + this.allowDuplicateKey = false; + this.deprecateDuplicateKey = true; + } + + this.freeze = opts.getBool("freeze", false); + this.onLoadProc = opts.getProc("on_load"); + + this.decimalClass = opts.getClass("decimal_class", null); + + if (decimalClass == null) { + this.decimalFactory = this::createFloat; + } else if (decimalClass == runtime.getClass("BigDecimal")) { + this.decimalFactory = this::createBigDecimal; + } else { + this.decimalFactory = this::createCustomDecimal; + } + + return this; + } + + public IRubyObject onLoad(ThreadContext context, IRubyObject object) { + if (onLoadProc == null) { + return object; + } else { + return onLoadProc.call(context, object); + } + } + + /** + * 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) { + Encoding encoding = source.getEncoding(); + if (encoding == ASCIIEncoding.INSTANCE) { + source = (RubyString) source.dup(); + source.setEncoding(UTF8Encoding.INSTANCE); + source.clearCodeRange(); + } else if (encoding != UTF8Encoding.INSTANCE) { + source = (RubyString) source.encode(context, context.runtime.getEncodingService().convertEncodingToRubyEncoding(UTF8Encoding.INSTANCE)); + } + return source; + } + + /** + * Parser#parse() + * + *

Parses the current JSON text source and returns the + * complete data structure as a result. + */ + @JRubyMethod + public IRubyObject parse(ThreadContext context, IRubyObject source) { + return new ParserSession(this, convertEncoding(context, source.convertToString()), info).parse(context); + } + + private RubyFloat createFloat(final ThreadContext context, final ByteList num) { + return RubyFloat.newFloat(context.runtime, new DoubleConverter().parse(num, true, true)); + } + + private IRubyObject createBigDecimal(final ThreadContext context, final ByteList num) { + final Ruby runtime = context.runtime; + return runtime.getKernel().callMethod(context, "BigDecimal", runtime.newString(num)); + } + + private IRubyObject createCustomDecimal(final ThreadContext context, final ByteList num) { + return decimalClass.newInstance(context, context.runtime.newString(num), Block.NULL_BLOCK); + } + + /** + * A single parsing session over one source string. + * + *

Once a ParserSession is instantiated, the source string should not + * change until the parsing is complete. The ParserSession object assumes + * the source {@link RubyString} is still associated to its original + * {@link ByteList}, which in turn must still be bound to the same + * byte[] value (and on the same offset). + */ + private static final class ParserSession { + private static final int INITIAL_FRAME_CAPACITY = 32; + private static final int INITIAL_VALUE_CAPACITY = 64; + + // Integers with fewer than this many digits are built directly from a + // long accumulated during the digit scan; longer ones fall back to + // ConvertBytes (Bignum). 17 digits (< 1e17) always fit a signed long, + // including after negation. Mirrors parser.c's MAX_FAST_INTEGER_SIZE. + private static final int MAX_FAST_INTEGER_SIZE = 18; + + // Same idea as the rvalue_cache in the C extension. + // + // This is bigger than the C implementation as this is + // heap allocated. + private static final int KEY_CACHE_CAPA = 128; + private static final int KEY_CACHE_MAX_ENTRY_LENGTH = 55; + + private static final long SPACES = 0x2020202020202020L; + + private enum FrameType { + ROOT(FramePhase.DONE), + ARRAY(FramePhase.ARRAY_COMMA), + OBJECT(FramePhase.OBJECT_COMMA); + + private final FramePhase nextPhase; + + FrameType(FramePhase nextPhase) { + this.nextPhase = nextPhase; + } + + FramePhase nextPhase() { + return nextPhase; + } + } + + private enum FramePhase { + DONE, + ARRAY_COMMA, + OBJECT_COMMA, + VALUE, + OBJECT_KEY, + OBJECT_COLON + } + + private static final class Frame { + FrameType type; + FramePhase phase; + // The position within the value stack when this frame was created. + int valueStackHead; + // The cursor position when we encountered a '{'. + // Used for error message reporting when decoding an JSON object. + int startCursor; + } + + private final Parser config; + private final RuntimeInfo info; + private final ByteList byteList; + private final ByteList view; + private final byte[] data; + + // Little-endian view over {@link #data}, used by the SWAR string scan + // so that {@link Long#numberOfTrailingZeros} locates the first + // interesting" byte within an 8-byte chunk. + private final ByteBuffer chunks; + private final StringScanner scanner; + private final StringDecoder decoder; + private final int begin; + private final int end; + private int cursor; + + private ThreadContext context; + + // Integer value accumulated by the most recent scanDigits() call. + private long digitsValue; + private int currentNesting = 0; + + private int inArray = 0; + + private final IRubyObject[] keyCache = new IRubyObject[KEY_CACHE_CAPA]; + private int keyCacheLength = 0; + private int emittedDeprecations = 0; + + + private Frame[] frameStack = new Frame[INITIAL_FRAME_CAPACITY]; + private int frameDepth = 0; + + private IRubyObject[] valueStack = new IRubyObject[INITIAL_VALUE_CAPACITY]; + private int valueTop = 0; + + private ParserSession(Parser config, RubyString source, RuntimeInfo info) { + this.config = config; + this.info = info; + this.byteList = source.getByteList(); + this.data = byteList.unsafeBytes(); + this.chunks = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN); + this.scanner = StringScanner.getInstance(); + this.view = new ByteList(data, false); + this.begin = byteList.begin(); + this.end = begin + byteList.length(); + this.decoder = new StringDecoder(config.allowControlCharacters, config.allowInvalidEscape); + } + + public IRubyObject parse(ThreadContext context) { + this.context = context; + this.cursor = begin; + pushFrame(FrameType.ROOT, FramePhase.VALUE, 0, -1); + + IRubyObject result = run(); + + // Only trailing whitespace (and comments) may follow the document. + eatWhitespace(); + if (cursor < end) { + throw unexpectedToken(cursor, end); + } + return result; + } + + private IRubyObject run() { + while (true) { + Frame frame = topFrame(); + + switch (frame.phase) { + case DONE: + return valueStack[valueTop - 1]; + + case VALUE: { + eatWhitespace(); + IRubyObject value; + byte c = peek(); + switch (c) { + case 'n': + if (matchKeyword("null")) { value = context.nil; break; } + throw unexpectedToken(cursor, end); + case 't': + if (matchKeyword("true")) { value = context.tru; break; } + throw unexpectedToken(cursor, end); + case 'f': + if (matchKeyword("false")) { value = context.fals; break; } + throw unexpectedToken(cursor, end); + case 'N': + if (config.allowNaN && matchKeyword("NaN")) { + value = getConstant(CONST_NAN); + break; + } + throw unexpectedToken(cursor, end); + case 'I': + if (config.allowNaN && matchKeyword("Infinity")) { + value = getConstant(CONST_INFINITY); + break; + } + throw unexpectedToken(cursor, end); + case '-': + cursor++; + if (config.allowNaN && matchKeyword("Infinity")) { + value = getConstant(CONST_MINUS_INFINITY); + } else { + value = parseNumber(cursor - 1, peek(), true); + } + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + value = parseNumber(cursor, c, false); + break; + case '"': + value = parseString(false); + break; + case '[': { + cursor++; + eatWhitespace(); + if (peek() == ']') { + cursor++; + value = decodeArray(0); + break; + } + currentNesting++; + checkNesting(); + inArray++; + // Phase stays VALUE: the next iteration reads + // the first element. + pushFrame(FrameType.ARRAY, FramePhase.VALUE, valueTop, -1); + continue; + } + case '{': { + int objectStart = cursor; + cursor++; + eatWhitespace(); + if (peek() == '}') { + cursor++; + value = decodeObject(0); + break; + } + currentNesting++; + checkNesting(); + // Phase KEY: the next iteration reads the first key. + pushFrame(FrameType.OBJECT, FramePhase.OBJECT_KEY, valueTop, objectStart); + continue; + } + case 0: + throw newException(Utils.M_PARSER_ERROR, "unexpected end of input"); + default: + throw unexpectedToken(cursor, end); + } + + pushValue(value); + valueCompleted(frame); + continue; + } + + case OBJECT_KEY: { + eatWhitespace(); + if (peek() == '"') { + pushValue(parseString(true)); + frame.phase = FramePhase.OBJECT_COLON; + continue; + } + throw unexpectedToken(cursor, end); + } + + case OBJECT_COLON: { + eatWhitespace(); + if (peek() == ':') { + cursor++; + frame.phase = FramePhase.VALUE; + continue; + } + throw unexpectedToken(cursor, end); + } + + case ARRAY_COMMA: { + eatWhitespace(); + byte b = peek(); + if (b == ',') { + cursor++; + if (config.allowTrailingComma) { + eatWhitespace(); + if (peek() == ']') { + // Trailing comma: re-enter COMMA to close. + continue; + } + } + frame.phase = FramePhase.VALUE; + continue; + } else if (b == ']') { + cursor++; + int count = entryCount(frame); + currentNesting--; + inArray--; + popFrame(); + pushValue(decodeArray(count)); + valueCompleted(topFrame()); + continue; + } + throw unexpectedToken(cursor, end); + } + + case OBJECT_COMMA: { + eatWhitespace(); + byte b = peek(); + if (b == ',') { + cursor++; + if (config.allowTrailingComma) { + eatWhitespace(); + if (peek() == '}') { + // Trailing comma: re-enter COMMA to close. + continue; + } + } + frame.phase = FramePhase.OBJECT_KEY; + continue; + } else if (b == '}') { + cursor++; + currentNesting--; + int count = entryCount(frame); + // Temporarily rewind the cursor so a duplicate-key + // error points at the object's opening brace. + int finalCursor = cursor; + cursor = frame.startCursor; + IRubyObject object = decodeObject(count); + cursor = finalCursor; + popFrame(); + pushValue(object); + valueCompleted(topFrame()); + continue; + } + throw unexpectedToken(cursor, end); + } + + default: + throw context.runtime.newRuntimeError("unreachable parser state"); + } + } + } + + private void pushValue(IRubyObject value) { + if (valueTop == valueStack.length) { + valueStack = Arrays.copyOf(valueStack, valueStack.length * 2); + } + valueStack[valueTop++] = config.onLoad(context, value); + } + + private void valueCompleted(Frame frame) { + frame.phase = frame.type.nextPhase(); + } + + private int entryCount(Frame frame) { + return valueTop - frame.valueStackHead; + } + + private Frame topFrame() { + return frameStack[frameDepth - 1]; + } + + private void pushFrame(FrameType type, FramePhase phase, + int valueStackHead, int startCursor) { + if (frameDepth == frameStack.length) { + frameStack = Arrays.copyOf(frameStack, frameStack.length * 2); + } + Frame frame = frameStack[frameDepth]; + if (frame == null) { + frame = new Frame(); + frameStack[frameDepth] = frame; + } + frame.type = type; + frame.phase = phase; + frame.valueStackHead = valueStackHead; + frame.startCursor = startCursor; + frameDepth++; + } + + private void popFrame() { + frameDepth--; + } + + private void checkNesting() { + if (config.maxNesting > 0 && currentNesting > config.maxNesting) { + throw newException(Utils.M_NESTING_ERROR, + "nesting of " + currentNesting + " is too deep"); + } + } + + private IRubyObject decodeArray(int count) { + int base = valueTop - count; + IRubyObject[] elements = new IRubyObject[count]; + System.arraycopy(valueStack, base, elements, 0, count); + valueTop = base; + RubyArray array = RubyArray.newArrayNoCopy(context.runtime, elements); + if (config.freeze) { + array.setFrozen(true); + } + return array; + } + + private IRubyObject decodeObject(int count) { + final Ruby runtime = context.runtime; + int base = valueTop - count; + int limit = valueTop; + RubyHash hash = RubyHash.newHash(runtime); + for (int i = base; i < limit; i += 2) { + // We use RubyHash#fastASet because all object keys have already been + // frozen and deduplicated. + // + // This was significantly faster than RubyHash#op_aset. + hash.fastASet(valueStack[i], valueStack[i + 1]); + } + valueTop = base; + + if (!config.allowDuplicateKey && hash.size() < count / 2) { + onDuplicateKey(findDuplicateKey(base, limit)); + } + + if (config.freeze) { + hash.setFrozen(true); + } + return hash; + } + + private IRubyObject findDuplicateKey(int base, int limit) { + RubyHash seen = RubyHash.newHash(context.runtime); + for (int i = base; i < limit; i += 2) { + int before = seen.size(); + IRubyObject key = valueStack[i]; + seen.fastASetCheckString(context.runtime, key, context.tru); + if (seen.size() == before) { + return key; + } + } + return context.nil; + } + + private void onDuplicateKey(IRubyObject key) { + // Symbol keys are reported by their string form (":a" -> "a") to + // match the C parser's message. + String keyInspect = key.callMethod(context, "to_s") + .callMethod(context, "inspect").asJavaString(); + if (config.deprecateDuplicateKey) { + if (emittedDeprecations < MAX_DEPRECATIONS) { + emittedDeprecations++; + context.runtime.getWarnings().warning( + "detected duplicate key " + keyInspect + " in JSON object. " + + "This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`"); + } + } else { + throw newException(Utils.M_PARSER_ERROR, "duplicate key " + keyInspect); + } + } + + private int parseDigits(long value) { + int start = cursor; + byte c = peek(); + for (; c >= '0' && c <= '9'; c = advance()) { + value = value * 10 + (c - '0'); + } + digitsValue = value; + return cursor - start; + } + + private IRubyObject parseNumber(int numberStart, byte first, boolean negative) { + int mantissaDigits = parseDigits(0); + long mantissa = digitsValue; + + if ((first == '0' && mantissaDigits > 1) || mantissaDigits == 0) { + throw unexpectedToken(numberStart, end); + } + + boolean integer = true; + int decimal_point_pos = -1; + + if (peek() == '.') { + integer = false; + decimal_point_pos = mantissaDigits; + cursor++; + int fracDigits = parseDigits(mantissa); + if (fracDigits == 0) { + throw unexpectedToken(numberStart, end); + } + mantissaDigits += fracDigits; + mantissa = digitsValue; + } + + int c = peek(); + long exponent = 0; + if (c == 'e' || c == 'E') { + integer = false; + c = advance(); + boolean negative_exponent = c == '-'; + if (negative_exponent || c == '+') advance(); + + int exponent_digits = parseDigits(0); + long abs_exponent = digitsValue; + if (exponent_digits == 0) { + throw unexpectedToken(numberStart, end); + } + + if (exponent_digits >= 20 || Long.compareUnsigned(abs_exponent, Long.MAX_VALUE) > 0) { + exponent = negative_exponent ? Long.MIN_VALUE : Long.MAX_VALUE; + } else { + exponent = negative_exponent ? -abs_exponent : abs_exponent; + } + } + + if (integer) { + if (mantissaDigits < MAX_FAST_INTEGER_SIZE) { + return context.runtime.newFixnum(negative ? -mantissa : mantissa); + } + return ConvertBytes.byteListToInum(context.runtime, absSubSequence(numberStart, cursor), 10, true); + } + + // Adjust exponent based on decimal point position + if (decimal_point_pos >= 0) exponent -= (mantissaDigits - decimal_point_pos); + + return decodeFloat(context, mantissa, mantissaDigits, exponent, negative, numberStart); + } + + IRubyObject decodeFloat(ThreadContext context, long mantissa, int mantissa_digits, long exponent, boolean negative, int start) { + if (config.decimalClass != null) return config.decimalFactory.apply(context, absSubSequence(start, cursor)); + if (exponent > Integer.MAX_VALUE) return getConstant(negative ? CONST_MINUS_INFINITY : CONST_INFINITY); + if (exponent < Integer.MIN_VALUE) return RubyFloat.newFloat(context.runtime, negative ? -0.0 : 0.0); + + // Ryu has rounding issues with subnormals around 1e-310 (< 2.225e-308) + if (mantissa_digits > 17 || mantissa_digits + exponent < -307) { + return RubyFloat.newFloat(context.runtime, Double.parseDouble(new String(data, start, cursor - start))); + } + + return RubyFloat.newFloat(context.runtime, ryuS2dFromParts(mantissa, mantissa_digits, (int) exponent, negative)); + } + + private IRubyObject parseString(boolean isName) { + final byte[] data = this.data; + final int contentStart = cursor + 1; // skip opening quote + + // The scanner finds the closing quote and reports whether the body + // is plain printable ASCII (no escape, no ASCII control character, + // no non-ASCII byte). Anything non-plain is handed to StringDecoder, + // which performs the UTF-8/control validation, escape expansion, and + // error reporting. + long scanned = scanner.scan(data, chunks, contentStart, end); + final int q = (int) scanned; + if (q < 0) { + throw newException(Utils.M_PARSER_ERROR, + "unexpected end of input, expected closing \""); + } + + boolean plain = (scanned & StringScanner.PLAIN_BIT) != 0; + cursor = q + 1; // past closing quote + + // Note: When running multiple read-world benchmarks in the same JVM, + // this seems consistently faster than "if (isName && plain)" + // and only handling the ASCII-only path in the cache. + // + // Note 2: It's important that all object keys are frozen and deduplicated, + // decodeObject relies this. + if (isName) { + // Resolve the key's decoded bytes without building a RubyString + // yet, so a cache hit skips allocation entirely. + byte[] buf; + int off; + int len; + if (plain) { + buf = data; + off = contentStart; + len = q - contentStart; + } else { + ByteList decoded = decoder.decode(context, byteList, + contentStart - begin, q - begin); + buf = decoded.getUnsafeBytes(); + off = decoded.begin(); + len = decoded.realSize(); + } + // Same as the C extension. + if (inArray > 0 && len > 0 && len <= KEY_CACHE_MAX_ENTRY_LENGTH && isLetter(buf[off])) { + return cachedKey(buf, off, len); + } + return internedKey(buf, off, len); + } + + RubyString string; + if (plain) { + string = RubyString.newString(context.runtime, data, contentStart, + q - contentStart, UTF8Encoding.INSTANCE); + } else { + ByteList content = decoder.decode(context, byteList, + contentStart - begin, q - begin); + string = context.runtime.newString(content); + string.setEncoding(UTF8Encoding.INSTANCE); + string.clearCodeRange(); + } + + if (config.freeze) { + return context.runtime.freezeAndDedupString(string); + } + + return string; + } + + private static boolean isLetter(byte b) { + return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z'); + } + + private IRubyObject internedKey(byte[] buf, int off, int len) { + RubyString string = context.runtime.newString( + new ByteList(buf, off, len, UTF8Encoding.INSTANCE, true)); + if (config.symbolizeNames) { + return string.intern(); + } + string.setFrozen(true); + return context.runtime.freezeAndDedupString(string); + } + + private IRubyObject cachedKey(byte[] buf, int off, int len) { + int low = 0; + int high = keyCacheLength - 1; + while (low <= high) { + int mid = (low + high) >>> 1; + int cmp = compareKey(buf, off, len, keyCache[mid]); + if (cmp == 0) { + return keyCache[mid]; + } else if (cmp > 0) { + low = mid + 1; + } else { + high = mid - 1; + } + } + + IRubyObject key = internedKey(buf, off, len); + if (keyCacheLength < KEY_CACHE_CAPA) { + System.arraycopy(keyCache, low, keyCache, low + 1, keyCacheLength - low); + keyCache[low] = key; + keyCacheLength++; + } + return key; + } + + // Orders by length first, then unsigned byte value + private static int compareKey(byte[] buf, int off, int len, IRubyObject entry) { + ByteList eb = entry instanceof RubySymbol + ? ((RubySymbol) entry).getBytes() + : ((RubyString) entry).getByteList(); + int elen = eb.realSize(); + if (len != elen) { + return len - elen; + } + byte[] ebuf = eb.getUnsafeBytes(); + int ebeg = eb.begin(); + for (int i = 0; i < len; i++) { + int cmp = Byte.toUnsignedInt(buf[off + i]) - Byte.toUnsignedInt(ebuf[ebeg + i]); + if (cmp != 0) { + return cmp; + } + } + return 0; + } + + private byte peek() { + return cursor < end ? data[cursor] : 0; + } + private byte advance() { + cursor++; + return peek(); + } + + private boolean matchKeyword(String keyword) { + int len = keyword.length(); + if (end - cursor < len) { + return false; + } + for (int i = 0; i < len; i++) { + if (data[cursor + i] != (byte) keyword.charAt(i)) { + return false; + } + } + cursor += len; + return true; + } + + private void eatWhitespace() { + while (cursor < end) { + switch (data[cursor]) { + case ' ': + case '\t': + case '\r': + cursor++; + break; + case '\n': + cursor++; + // Same heuristic from the C parser: a newline in + // pretty-printed JSON is almost always followed by a run + // of indentation spaces, so skip them eight at a time. + while (cursor + 8 <= end) { + long x = chunks.getLong(cursor); + if (x == SPACES) { + cursor += 8; + } else { + cursor += Long.numberOfTrailingZeros(x ^ SPACES) >>> 3; + break; + } + } + break; + case '/': + eatComments(); + break; + default: + return; + } + } + } + + private void eatComments() { + if (!config.allowComments) { + if (config.deprecateComments) { + if (emittedDeprecations < MAX_DEPRECATIONS) { + emittedDeprecations++; + context.runtime.getWarnings().warning( + "Encountered comment in JSON. This will raise an error in json 3.0 unless enabled via `allow_comments: true`"); + } + } else { + throw unexpectedToken(cursor, end); + } + } + + int start = cursor; + cursor++; // skip '/' + switch (peek()) { + case '/': + cursor++; + while (cursor < end && data[cursor] != '\n') cursor++; + if (cursor < end) cursor++; // consume newline + break; + case '*': + cursor++; + while (true) { + while (cursor < end && data[cursor] != '*') cursor++; + if (cursor >= end) { + throw newException(Utils.M_PARSER_ERROR, + "unterminated comment, expected closing '*/'"); + } + cursor++; // past '*' + if (peek() == '/') { + cursor++; + break; + } + } + break; + default: + throw unexpectedToken(start, end); + } + } + + /** + * Updates the "view" ByteList with the new offsets and returns it. The + * returned ByteList must be consumed before the next call, since the + * same instance is reused. + */ + private ByteList absSubSequence(int absStart, int absEnd) { + view.setBegin(absStart); + view.setRealSize(absEnd - absStart); + return view; + } + + private IRubyObject getConstant(String name) { + return info.jsonModule.get().getConstant(name); + } + + private RaiseException parsingError(int absStart, int absEnd) { + RubyString msg = context.runtime.newString("unexpected token at '") + .cat(data, absStart, Math.min(absEnd - absStart, 32)) + .cat((byte)'\''); + return newException(Utils.M_PARSER_ERROR, msg); + } + + private RaiseException unexpectedToken(int absStart, int absEnd) { + return parsingError(absStart, absEnd); + } + + private RaiseException newException(String className, String message) { + return Utils.newException(context, className, message); + } + + private RaiseException newException(String className, RubyString message) { + return Utils.newException(context, className, message); + } + } +} diff --git a/java/src/json/ext/ParserConfig.java b/java/src/json/ext/ParserConfig.java deleted file mode 100644 index 562c0ab6..00000000 --- a/java/src/json/ext/ParserConfig.java +++ /dev/null @@ -1,2537 +0,0 @@ - -// line 1 "ParserConfig.rl" -/* - * This code is copyrighted work by Daniel Luz . - * - * Distributed under the Ruby license: https://www.ruby-lang.org/en/about/license.txt - */ -package json.ext; - -import org.jcodings.Encoding; -import org.jcodings.specific.ASCIIEncoding; -import org.jcodings.specific.UTF8Encoding; -import org.jruby.Ruby; -import org.jruby.RubyArray; -import org.jruby.RubyClass; -import org.jruby.RubyFloat; -import org.jruby.RubyHash; -import org.jruby.RubyInteger; -import org.jruby.RubyObject; -import org.jruby.RubyProc; -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.Helpers; -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 java.util.function.BiFunction; - -import static org.jruby.util.ConvertDouble.DoubleConverter; - -/** - * The JSON::Ext::Parser class. - * - *

This is the JSON parser implemented as a Java class. To use it as the - * standard parser, set - *

JSON.parser = JSON::Ext::Parser
- * This is performed for you when you include "json/ext". - * - *

This class does not perform the actual parsing, just acts as an interface - * to Ruby code. When the {@link #parse(ThreadContext)} method is invoked, a - * ParserConfig.ParserSession object is instantiated, which handles the process. - * - * @author mernen - */ -public class ParserConfig extends RubyObject { - private final RuntimeInfo info; - private int maxNesting; - private boolean allowNaN; - private boolean allowTrailingComma; - private boolean allowComments; - private boolean deprecateComments; - private boolean allowControlCharacters; - private boolean allowInvalidEscape; - private boolean allowDuplicateKey; - private boolean deprecateDuplicateKey; - private boolean symbolizeNames; - private boolean freeze; - private RubyProc onLoadProc; - private RubyClass decimalClass; - BiFunction decimalFactory; - private RubyHash match_string; - - private static final int DEFAULT_MAX_NESTING = 100; - - 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 = ParserConfig::new; - - /** - * Multiple-value return for internal parser methods. - * - *

All the parseStuff methods return instances of - * ParserResult when successful, or null when - * there's a problem with the input data. - */ - static final class ParserResult { - /** - * The result of the successful parsing. Should never be - * null. - */ - IRubyObject result; - /** - * The point where the parser returned. - */ - int p; - - void update(IRubyObject result, int p) { - this.result = result; - this.p = p; - } - } - - public ParserConfig(Ruby runtime, RubyClass metaClass) { - super(runtime, metaClass); - info = RuntimeInfo.forRuntime(runtime); - } - - /** - * ParserConfig.new(source, opts = {}) - * - *

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: - *

- *

- *
: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. - *

- *

:allow_trailing_comma - *
If set to true, allow arrays and objects with a trailing - * comma 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. - *

- *

:create_additions - *
If set to false, the Parser doesn't create additions - * even if a matching class and create_id was found. This option - * defaults to true. - *

- *

:object_class - *
Defaults to Hash. If another type is provided, it will be used - * instead of Hash to represent JSON objects. The type must respond to - * new without arguments, and return an object that respond to []=. - *

- *

:array_class - *
Defaults to Array. If another type is provided, it will be used - * instead of Hash to represent JSON arrays. The type must respond to - * new without arguments, and return an object that respond to <<. - *

- *

: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. - *
- */ - - @JRubyMethod(name = "new", meta = true) - public static IRubyObject newInstance(IRubyObject clazz, IRubyObject arg0, Block block) { - ParserConfig config = (ParserConfig)((RubyClass)clazz).allocate(); - - config.callInit(arg0, block); - - return config; - } - - @JRubyMethod(name = "new", meta = true) - public static IRubyObject newInstance(IRubyObject clazz, IRubyObject arg0, IRubyObject arg1, Block block) { - ParserConfig config = (ParserConfig)((RubyClass)clazz).allocate(); - - config.callInit(arg0, arg1, block); - - return config; - } - - @JRubyMethod(visibility = Visibility.PRIVATE) - public IRubyObject initialize(ThreadContext context, IRubyObject options) { - checkFrozen(); - Ruby runtime = context.runtime; - - OptionsReader opts = new OptionsReader(context, options); - this.maxNesting = opts.getInt("max_nesting", DEFAULT_MAX_NESTING); - this.allowNaN = opts.getBool("allow_nan", false); - if (opts.hasKey("allow_comments")) { - this.allowComments = opts.getBool("allow_comments", false); - this.deprecateComments = false; - } else { - this.allowComments = true; - this.deprecateComments = true; - } - - this.allowControlCharacters = opts.getBool("allow_control_characters", false); - this.allowInvalidEscape = opts.getBool("allow_invalid_escape", false); - this.allowTrailingComma = opts.getBool("allow_trailing_comma", false); - this.symbolizeNames = opts.getBool("symbolize_names", false); - if (opts.hasKey("allow_duplicate_key")) { - this.allowDuplicateKey = opts.getBool("allow_duplicate_key", false); - this.deprecateDuplicateKey = false; - } else { - this.allowDuplicateKey = false; - this.deprecateDuplicateKey = true; - } - - this.freeze = opts.getBool("freeze", false); - this.onLoadProc = opts.getProc("on_load"); - - this.decimalClass = opts.getClass("decimal_class", null); - - if (decimalClass == null) { - this.decimalFactory = this::createFloat; - } else if (decimalClass == runtime.getClass("BigDecimal")) { - this.decimalFactory = this::createBigDecimal; - } else { - this.decimalFactory = this::createCustomDecimal; - } - - return this; - } - - public IRubyObject onLoad(ThreadContext context, IRubyObject object) { - if (onLoadProc == null) { - return object; - } else { - return onLoadProc.call(context, object); - } - } - - /** - * 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) { - Encoding encoding = source.getEncoding(); - if (encoding == ASCIIEncoding.INSTANCE) { - source = (RubyString) source.dup(); - source.setEncoding(UTF8Encoding.INSTANCE); - source.clearCodeRange(); - } else if (encoding != UTF8Encoding.INSTANCE) { - source = (RubyString) source.encode(context, context.runtime.getEncodingService().convertEncodingToRubyEncoding(UTF8Encoding.INSTANCE)); - } - return source; - } - - /** - * Parser#parse() - * - *

Parses the current JSON text source and returns the - * complete data structure as a result. - */ - @JRubyMethod - public IRubyObject parse(ThreadContext context, IRubyObject source) { - return new ParserSession(this, convertEncoding(context, source.convertToString()), context, info).parse(context); - } - - /** - * Queries JSON.create_id. Returns null if it is - * set to nil or false, and a String if not. - */ - private RubyString getCreateId(ThreadContext context) { - IRubyObject v = info.jsonModule.get().callMethod(context, "create_id"); - return v.isTrue() ? v.convertToString() : null; - } - - private RubyFloat createFloat(final ThreadContext context, final ByteList num) { - return RubyFloat.newFloat(context.runtime, new DoubleConverter().parse(num, true, true)); - } - - private IRubyObject createBigDecimal(final ThreadContext context, final ByteList num) { - final Ruby runtime = context.runtime; - return runtime.getKernel().callMethod(context, "BigDecimal", runtime.newString(num)); - } - - private IRubyObject createCustomDecimal(final ThreadContext context, final ByteList num) { - return decimalClass.newInstance(context, context.runtime.newString(num), Block.NULL_BLOCK); - } - - /** - * A string parsing session. - * - *

Once a ParserSession is instantiated, the source string should not - * change until the parsing is complete. The ParserSession object assumes - * the source {@link RubyString} is still associated to its original - * {@link ByteList}, which in turn must still be bound to the same - * byte[] value (and on the same offset). - */ - // Ragel uses lots of fall-through - @SuppressWarnings("fallthrough") - private static class ParserSession { - private final ParserConfig config; - private final RuntimeInfo info; - private final ByteList byteList; - private final ByteList view; - private final byte[] data; - private final StringDecoder decoder; - private int currentNesting = 0; - private int emittedDeprecations = 0; - - private ParserSession(ParserConfig config, RubyString source, ThreadContext context, RuntimeInfo info) { - this.config = config; - this.info = info; - this.byteList = source.getByteList(); - this.data = byteList.unsafeBytes(); - this.view = new ByteList(data, false); - this.decoder = new StringDecoder(config.allowControlCharacters, config.allowInvalidEscape); - } - - private RaiseException parsingError(ThreadContext context, String message, int absStart, int absEnd) { - RubyString msg = context.runtime.newString("unexpected token at '") - .cat(data, absStart, Math.min(absEnd - absStart, 32)) - .cat((byte)'\''); - return newException(context, Utils.M_PARSER_ERROR, msg); - } - - private RaiseException unexpectedToken(ThreadContext context, int absStart, int absEnd) { - return parsingError(context, "unexpected token at '", absStart, absEnd); - } - - -// line 360 "ParserConfig.rl" - - - -// line 326 "ParserConfig.java" -private static byte[] init__JSON_value_actions_0() -{ - return new byte [] { - 0, 1, 0, 1, 1, 1, 2, 1, 3, 1, 4, 1, - 5, 1, 6, 1, 7, 1, 8, 1, 9 - }; -} - -private static final byte _JSON_value_actions[] = init__JSON_value_actions_0(); - - -private static byte[] init__JSON_value_key_offsets_0() -{ - return new byte [] { - 0, 0, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, - 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 - }; -} - -private static final byte _JSON_value_key_offsets[] = init__JSON_value_key_offsets_0(); - - -private static char[] init__JSON_value_trans_keys_0() -{ - return new char [] { - 34, 45, 73, 78, 91, 102, 110, 116, 123, 48, 57, 110, - 102, 105, 110, 105, 116, 121, 97, 78, 97, 108, 115, 101, - 117, 108, 108, 114, 117, 101, 0 - }; -} - -private static final char _JSON_value_trans_keys[] = init__JSON_value_trans_keys_0(); - - -private static byte[] init__JSON_value_single_lengths_0() -{ - return new byte [] { - 0, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 - }; -} - -private static final byte _JSON_value_single_lengths[] = init__JSON_value_single_lengths_0(); - - -private static byte[] init__JSON_value_range_lengths_0() -{ - return new byte [] { - 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; -} - -private static final byte _JSON_value_range_lengths[] = init__JSON_value_range_lengths_0(); - - -private static byte[] init__JSON_value_index_offsets_0() -{ - return new byte [] { - 0, 0, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, - 31, 33, 35, 37, 39, 41, 43, 45, 47, 49 - }; -} - -private static final byte _JSON_value_index_offsets[] = init__JSON_value_index_offsets_0(); - - -private static byte[] init__JSON_value_trans_targs_0() -{ - return new byte [] { - 21, 21, 2, 9, 21, 11, 15, 18, 21, 21, 0, 3, - 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 21, - 0, 10, 0, 21, 0, 12, 0, 13, 0, 14, 0, 21, - 0, 16, 0, 17, 0, 21, 0, 19, 0, 20, 0, 21, - 0, 0, 0 - }; -} - -private static final byte _JSON_value_trans_targs[] = init__JSON_value_trans_targs_0(); - - -private static byte[] init__JSON_value_trans_actions_0() -{ - return new byte [] { - 13, 11, 0, 0, 15, 0, 0, 0, 17, 11, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, - 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 3, - 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 5, - 0, 0, 0 - }; -} - -private static final byte _JSON_value_trans_actions[] = init__JSON_value_trans_actions_0(); - - -private static byte[] init__JSON_value_from_state_actions_0() -{ - return new byte [] { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 19 - }; -} - -private static final byte _JSON_value_from_state_actions[] = init__JSON_value_from_state_actions_0(); - - -static final int JSON_value_start = 1; -static final int JSON_value_first_final = 21; -static final int JSON_value_error = 0; - -static final int JSON_value_en_main = 1; - - -// line 466 "ParserConfig.rl" - - - void parseValue(ThreadContext context, ParserResult res, int p, int pe) { - int cs; - IRubyObject result = null; - - -// line 448 "ParserConfig.java" - { - cs = JSON_value_start; - } - -// line 473 "ParserConfig.rl" - -// line 455 "ParserConfig.java" - { - int _klen; - int _trans = 0; - int _acts; - int _nacts; - int _keys; - int _goto_targ = 0; - - _goto: while (true) { - switch ( _goto_targ ) { - case 0: - if ( p == pe ) { - _goto_targ = 4; - continue _goto; - } - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } -case 1: - _acts = _JSON_value_from_state_actions[cs]; - _nacts = (int) _JSON_value_actions[_acts++]; - while ( _nacts-- > 0 ) { - switch ( _JSON_value_actions[_acts++] ) { - case 9: -// line 451 "ParserConfig.rl" - { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } - break; -// line 487 "ParserConfig.java" - } - } - - _match: do { - _keys = _JSON_value_key_offsets[cs]; - _trans = _JSON_value_index_offsets[cs]; - _klen = _JSON_value_single_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + _klen - 1; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + ((_upper-_lower) >> 1); - if ( data[p] < _JSON_value_trans_keys[_mid] ) - _upper = _mid - 1; - else if ( data[p] > _JSON_value_trans_keys[_mid] ) - _lower = _mid + 1; - else { - _trans += (_mid - _keys); - break _match; - } - } - _keys += _klen; - _trans += _klen; - } - - _klen = _JSON_value_range_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + (_klen<<1) - 2; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + (((_upper-_lower) >> 1) & ~1); - if ( data[p] < _JSON_value_trans_keys[_mid] ) - _upper = _mid - 2; - else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) - _lower = _mid + 2; - else { - _trans += ((_mid - _keys)>>1); - break _match; - } - } - _trans += _klen; - } - } while (false); - - cs = _JSON_value_trans_targs[_trans]; - - if ( _JSON_value_trans_actions[_trans] != 0 ) { - _acts = _JSON_value_trans_actions[_trans]; - _nacts = (int) _JSON_value_actions[_acts++]; - while ( _nacts-- > 0 ) - { - switch ( _JSON_value_actions[_acts++] ) - { - case 0: -// line 368 "ParserConfig.rl" - { - result = context.nil; - } - break; - case 1: -// line 371 "ParserConfig.rl" - { - result = context.fals; - } - break; - case 2: -// line 374 "ParserConfig.rl" - { - result = context.tru; - } - break; - case 3: -// line 377 "ParserConfig.rl" - { - if (config.allowNaN) { - result = getConstant(CONST_NAN); - } else { - throw unexpectedToken(context, p - 2, pe); - } - } - break; - case 4: -// line 384 "ParserConfig.rl" - { - if (config.allowNaN) { - result = getConstant(CONST_INFINITY); - } else { - throw unexpectedToken(context, p - 7, pe); - } - } - break; - case 5: -// line 391 "ParserConfig.rl" - { - if (pe > p + 8 && - absSubSequence(p, p + 9).equals(JSON_MINUS_INFINITY)) { - - if (config.allowNaN) { - result = getConstant(CONST_MINUS_INFINITY); - {p = (( p + 10))-1;} - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } else { - throw unexpectedToken(context, p, pe); - } - } - parseFloat(context, res, p, pe); - if (res.result != null) { - result = res.result; - {p = (( res.p))-1;} - } - parseInteger(context, res, p, pe); - if (res.result != null) { - result = res.result; - {p = (( res.p))-1;} - } - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } - break; - case 6: -// line 417 "ParserConfig.rl" - { - parseString(context, res, p, pe); - if (res.result == null) { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } else { - result = res.result; - {p = (( res.p))-1;} - } - } - break; - case 7: -// line 427 "ParserConfig.rl" - { - currentNesting++; - parseArray(context, res, p, pe); - currentNesting--; - if (res.result == null) { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } else { - result = res.result; - {p = (( res.p))-1;} - } - } - break; - case 8: -// line 439 "ParserConfig.rl" - { - currentNesting++; - parseObject(context, res, p, pe); - currentNesting--; - if (res.result == null) { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } else { - result = res.result; - {p = (( res.p))-1;} - } - } - break; -// line 659 "ParserConfig.java" - } - } - } - -case 2: - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } - if ( ++p != pe ) { - _goto_targ = 1; - continue _goto; - } -case 4: -case 5: - } - break; } - } - -// line 474 "ParserConfig.rl" - - if (cs >= JSON_value_first_final && result != null) { - if (config.freeze) { - result.setFrozen(true); - } - res.update(result, p); - } else { - res.update(null, p); - } - } - - -// line 692 "ParserConfig.java" -private static byte[] init__JSON_integer_actions_0() -{ - return new byte [] { - 0, 1, 0 - }; -} - -private static final byte _JSON_integer_actions[] = init__JSON_integer_actions_0(); - - -private static byte[] init__JSON_integer_key_offsets_0() -{ - return new byte [] { - 0, 0, 4, 7, 9, 9 - }; -} - -private static final byte _JSON_integer_key_offsets[] = init__JSON_integer_key_offsets_0(); - - -private static char[] init__JSON_integer_trans_keys_0() -{ - return new char [] { - 45, 48, 49, 57, 48, 49, 57, 48, 57, 48, 57, 0 - }; -} - -private static final char _JSON_integer_trans_keys[] = init__JSON_integer_trans_keys_0(); - - -private static byte[] init__JSON_integer_single_lengths_0() -{ - return new byte [] { - 0, 2, 1, 0, 0, 0 - }; -} - -private static final byte _JSON_integer_single_lengths[] = init__JSON_integer_single_lengths_0(); - - -private static byte[] init__JSON_integer_range_lengths_0() -{ - return new byte [] { - 0, 1, 1, 1, 0, 1 - }; -} - -private static final byte _JSON_integer_range_lengths[] = init__JSON_integer_range_lengths_0(); - - -private static byte[] init__JSON_integer_index_offsets_0() -{ - return new byte [] { - 0, 0, 4, 7, 9, 10 - }; -} - -private static final byte _JSON_integer_index_offsets[] = init__JSON_integer_index_offsets_0(); - - -private static byte[] init__JSON_integer_indicies_0() -{ - return new byte [] { - 0, 2, 3, 1, 2, 3, 1, 1, 4, 1, 3, 4, - 0 - }; -} - -private static final byte _JSON_integer_indicies[] = init__JSON_integer_indicies_0(); - - -private static byte[] init__JSON_integer_trans_targs_0() -{ - return new byte [] { - 2, 0, 3, 5, 4 - }; -} - -private static final byte _JSON_integer_trans_targs[] = init__JSON_integer_trans_targs_0(); - - -private static byte[] init__JSON_integer_trans_actions_0() -{ - return new byte [] { - 0, 0, 0, 0, 1 - }; -} - -private static final byte _JSON_integer_trans_actions[] = init__JSON_integer_trans_actions_0(); - - -static final int JSON_integer_start = 1; -static final int JSON_integer_first_final = 3; -static final int JSON_integer_error = 0; - -static final int JSON_integer_en_main = 1; - - -// line 496 "ParserConfig.rl" - - - void parseInteger(ThreadContext context, ParserResult res, int p, int pe) { - int new_p = parseIntegerInternal(p, pe); - if (new_p == -1) { - res.update(null, p); - return; - } - RubyInteger number = createInteger(context, p, new_p); - res.update(config.onLoad(context, number), new_p + 1); - } - - int parseIntegerInternal(int p, int pe) { - int cs; - - -// line 808 "ParserConfig.java" - { - cs = JSON_integer_start; - } - -// line 512 "ParserConfig.rl" - int memo = p; - -// line 816 "ParserConfig.java" - { - int _klen; - int _trans = 0; - int _acts; - int _nacts; - int _keys; - int _goto_targ = 0; - - _goto: while (true) { - switch ( _goto_targ ) { - case 0: - if ( p == pe ) { - _goto_targ = 4; - continue _goto; - } - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } -case 1: - _match: do { - _keys = _JSON_integer_key_offsets[cs]; - _trans = _JSON_integer_index_offsets[cs]; - _klen = _JSON_integer_single_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + _klen - 1; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + ((_upper-_lower) >> 1); - if ( data[p] < _JSON_integer_trans_keys[_mid] ) - _upper = _mid - 1; - else if ( data[p] > _JSON_integer_trans_keys[_mid] ) - _lower = _mid + 1; - else { - _trans += (_mid - _keys); - break _match; - } - } - _keys += _klen; - _trans += _klen; - } - - _klen = _JSON_integer_range_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + (_klen<<1) - 2; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + (((_upper-_lower) >> 1) & ~1); - if ( data[p] < _JSON_integer_trans_keys[_mid] ) - _upper = _mid - 2; - else if ( data[p] > _JSON_integer_trans_keys[_mid+1] ) - _lower = _mid + 2; - else { - _trans += ((_mid - _keys)>>1); - break _match; - } - } - _trans += _klen; - } - } while (false); - - _trans = _JSON_integer_indicies[_trans]; - cs = _JSON_integer_trans_targs[_trans]; - - if ( _JSON_integer_trans_actions[_trans] != 0 ) { - _acts = _JSON_integer_trans_actions[_trans]; - _nacts = (int) _JSON_integer_actions[_acts++]; - while ( _nacts-- > 0 ) - { - switch ( _JSON_integer_actions[_acts++] ) - { - case 0: -// line 490 "ParserConfig.rl" - { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } - break; -// line 903 "ParserConfig.java" - } - } - } - -case 2: - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } - if ( ++p != pe ) { - _goto_targ = 1; - continue _goto; - } -case 4: -case 5: - } - break; } - } - -// line 514 "ParserConfig.rl" - - if (cs < JSON_integer_first_final) { - return -1; - } - - return p; - } - - RubyInteger createInteger(ThreadContext context, int p, int new_p) { - Ruby runtime = context.runtime; - ByteList num = absSubSequence(p, new_p); - return bytesToInum(runtime, num); - } - - RubyInteger bytesToInum(Ruby runtime, ByteList num) { - return ConvertBytes.byteListToInum(runtime, num, 10, true); - } - - -// line 943 "ParserConfig.java" -private static byte[] init__JSON_float_actions_0() -{ - return new byte [] { - 0, 1, 0 - }; -} - -private static final byte _JSON_float_actions[] = init__JSON_float_actions_0(); - - -private static byte[] init__JSON_float_key_offsets_0() -{ - return new byte [] { - 0, 0, 4, 7, 10, 12, 16, 18, 23, 29, 29 - }; -} - -private static final byte _JSON_float_key_offsets[] = init__JSON_float_key_offsets_0(); - - -private static char[] init__JSON_float_trans_keys_0() -{ - return new char [] { - 45, 48, 49, 57, 48, 49, 57, 46, 69, 101, 48, 57, - 43, 45, 48, 57, 48, 57, 46, 69, 101, 48, 57, 69, - 101, 45, 46, 48, 57, 69, 101, 45, 46, 48, 57, 0 - }; -} - -private static final char _JSON_float_trans_keys[] = init__JSON_float_trans_keys_0(); - - -private static byte[] init__JSON_float_single_lengths_0() -{ - return new byte [] { - 0, 2, 1, 3, 0, 2, 0, 3, 2, 0, 2 - }; -} - -private static final byte _JSON_float_single_lengths[] = init__JSON_float_single_lengths_0(); - - -private static byte[] init__JSON_float_range_lengths_0() -{ - return new byte [] { - 0, 1, 1, 0, 1, 1, 1, 1, 2, 0, 2 - }; -} - -private static final byte _JSON_float_range_lengths[] = init__JSON_float_range_lengths_0(); - - -private static byte[] init__JSON_float_index_offsets_0() -{ - return new byte [] { - 0, 0, 4, 7, 11, 13, 17, 19, 24, 29, 30 - }; -} - -private static final byte _JSON_float_index_offsets[] = init__JSON_float_index_offsets_0(); - - -private static byte[] init__JSON_float_indicies_0() -{ - return new byte [] { - 0, 2, 3, 1, 2, 3, 1, 4, 5, 5, 1, 6, - 1, 7, 7, 8, 1, 8, 1, 4, 5, 5, 3, 1, - 5, 5, 1, 6, 9, 1, 1, 1, 1, 8, 9, 0 - }; -} - -private static final byte _JSON_float_indicies[] = init__JSON_float_indicies_0(); - - -private static byte[] init__JSON_float_trans_targs_0() -{ - return new byte [] { - 2, 0, 3, 7, 4, 5, 8, 6, 10, 9 - }; -} - -private static final byte _JSON_float_trans_targs[] = init__JSON_float_trans_targs_0(); - - -private static byte[] init__JSON_float_trans_actions_0() -{ - return new byte [] { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 - }; -} - -private static final byte _JSON_float_trans_actions[] = init__JSON_float_trans_actions_0(); - - -static final int JSON_float_start = 1; -static final int JSON_float_first_final = 8; -static final int JSON_float_error = 0; - -static final int JSON_float_en_main = 1; - - -// line 547 "ParserConfig.rl" - - - void parseFloat(ThreadContext context, ParserResult res, int p, int pe) { - int new_p = parseFloatInternal(p, pe); - if (new_p == -1) { - res.update(null, p); - return; - } - final ByteList num = absSubSequence(p, new_p); - IRubyObject number = config.decimalFactory.apply(context, num); - - res.update(config.onLoad(context, number), new_p + 1); - } - - int parseFloatInternal(int p, int pe) { - int cs; - - -// line 1064 "ParserConfig.java" - { - cs = JSON_float_start; - } - -// line 565 "ParserConfig.rl" - int memo = p; - -// line 1072 "ParserConfig.java" - { - int _klen; - int _trans = 0; - int _acts; - int _nacts; - int _keys; - int _goto_targ = 0; - - _goto: while (true) { - switch ( _goto_targ ) { - case 0: - if ( p == pe ) { - _goto_targ = 4; - continue _goto; - } - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } -case 1: - _match: do { - _keys = _JSON_float_key_offsets[cs]; - _trans = _JSON_float_index_offsets[cs]; - _klen = _JSON_float_single_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + _klen - 1; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + ((_upper-_lower) >> 1); - if ( data[p] < _JSON_float_trans_keys[_mid] ) - _upper = _mid - 1; - else if ( data[p] > _JSON_float_trans_keys[_mid] ) - _lower = _mid + 1; - else { - _trans += (_mid - _keys); - break _match; - } - } - _keys += _klen; - _trans += _klen; - } - - _klen = _JSON_float_range_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + (_klen<<1) - 2; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + (((_upper-_lower) >> 1) & ~1); - if ( data[p] < _JSON_float_trans_keys[_mid] ) - _upper = _mid - 2; - else if ( data[p] > _JSON_float_trans_keys[_mid+1] ) - _lower = _mid + 2; - else { - _trans += ((_mid - _keys)>>1); - break _match; - } - } - _trans += _klen; - } - } while (false); - - _trans = _JSON_float_indicies[_trans]; - cs = _JSON_float_trans_targs[_trans]; - - if ( _JSON_float_trans_actions[_trans] != 0 ) { - _acts = _JSON_float_trans_actions[_trans]; - _nacts = (int) _JSON_float_actions[_acts++]; - while ( _nacts-- > 0 ) - { - switch ( _JSON_float_actions[_acts++] ) - { - case 0: -// line 538 "ParserConfig.rl" - { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } - break; -// line 1159 "ParserConfig.java" - } - } - } - -case 2: - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } - if ( ++p != pe ) { - _goto_targ = 1; - continue _goto; - } -case 4: -case 5: - } - break; } - } - -// line 567 "ParserConfig.rl" - - if (cs < JSON_float_first_final) { - return -1; - } - - return p; - } - - -// line 1189 "ParserConfig.java" -private static byte[] init__JSON_string_actions_0() -{ - return new byte [] { - 0, 2, 0, 1 - }; -} - -private static final byte _JSON_string_actions[] = init__JSON_string_actions_0(); - - -private static byte[] init__JSON_string_key_offsets_0() -{ - return new byte [] { - 0, 0, 1, 3, 4, 10, 16, 22, 28 - }; -} - -private static final byte _JSON_string_key_offsets[] = init__JSON_string_key_offsets_0(); - - -private static char[] init__JSON_string_trans_keys_0() -{ - return new char [] { - 34, 34, 92, 117, 48, 57, 65, 70, 97, 102, 48, 57, - 65, 70, 97, 102, 48, 57, 65, 70, 97, 102, 48, 57, - 65, 70, 97, 102, 0 - }; -} - -private static final char _JSON_string_trans_keys[] = init__JSON_string_trans_keys_0(); - - -private static byte[] init__JSON_string_single_lengths_0() -{ - return new byte [] { - 0, 1, 2, 1, 0, 0, 0, 0, 0 - }; -} - -private static final byte _JSON_string_single_lengths[] = init__JSON_string_single_lengths_0(); - - -private static byte[] init__JSON_string_range_lengths_0() -{ - return new byte [] { - 0, 0, 0, 0, 3, 3, 3, 3, 0 - }; -} - -private static final byte _JSON_string_range_lengths[] = init__JSON_string_range_lengths_0(); - - -private static byte[] init__JSON_string_index_offsets_0() -{ - return new byte [] { - 0, 0, 2, 5, 7, 11, 15, 19, 23 - }; -} - -private static final byte _JSON_string_index_offsets[] = init__JSON_string_index_offsets_0(); - - -private static byte[] init__JSON_string_indicies_0() -{ - return new byte [] { - 0, 1, 2, 3, 0, 4, 0, 5, 5, 5, 1, 6, - 6, 6, 1, 7, 7, 7, 1, 0, 0, 0, 1, 1, - 0 - }; -} - -private static final byte _JSON_string_indicies[] = init__JSON_string_indicies_0(); - - -private static byte[] init__JSON_string_trans_targs_0() -{ - return new byte [] { - 2, 0, 8, 3, 4, 5, 6, 7 - }; -} - -private static final byte _JSON_string_trans_targs[] = init__JSON_string_trans_targs_0(); - - -private static byte[] init__JSON_string_trans_actions_0() -{ - return new byte [] { - 0, 0, 1, 0, 0, 0, 0, 0 - }; -} - -private static final byte _JSON_string_trans_actions[] = init__JSON_string_trans_actions_0(); - - -static final int JSON_string_start = 1; -static final int JSON_string_first_final = 8; -static final int JSON_string_error = 0; - -static final int JSON_string_en_main = 1; - - -// line 606 "ParserConfig.rl" - - - void parseString(ThreadContext context, ParserResult res, int p, int pe) { - int cs; - IRubyObject result = null; - - -// line 1299 "ParserConfig.java" - { - cs = JSON_string_start; - } - -// line 613 "ParserConfig.rl" - int memo = p; - -// line 1307 "ParserConfig.java" - { - int _klen; - int _trans = 0; - int _acts; - int _nacts; - int _keys; - int _goto_targ = 0; - - _goto: while (true) { - switch ( _goto_targ ) { - case 0: - if ( p == pe ) { - _goto_targ = 4; - continue _goto; - } - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } -case 1: - _match: do { - _keys = _JSON_string_key_offsets[cs]; - _trans = _JSON_string_index_offsets[cs]; - _klen = _JSON_string_single_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + _klen - 1; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + ((_upper-_lower) >> 1); - if ( data[p] < _JSON_string_trans_keys[_mid] ) - _upper = _mid - 1; - else if ( data[p] > _JSON_string_trans_keys[_mid] ) - _lower = _mid + 1; - else { - _trans += (_mid - _keys); - break _match; - } - } - _keys += _klen; - _trans += _klen; - } - - _klen = _JSON_string_range_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + (_klen<<1) - 2; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + (((_upper-_lower) >> 1) & ~1); - if ( data[p] < _JSON_string_trans_keys[_mid] ) - _upper = _mid - 2; - else if ( data[p] > _JSON_string_trans_keys[_mid+1] ) - _lower = _mid + 2; - else { - _trans += ((_mid - _keys)>>1); - break _match; - } - } - _trans += _klen; - } - } while (false); - - _trans = _JSON_string_indicies[_trans]; - cs = _JSON_string_trans_targs[_trans]; - - if ( _JSON_string_trans_actions[_trans] != 0 ) { - _acts = _JSON_string_trans_actions[_trans]; - _nacts = (int) _JSON_string_actions[_acts++]; - while ( _nacts-- > 0 ) - { - switch ( _JSON_string_actions[_acts++] ) - { - case 0: -// line 581 "ParserConfig.rl" - { - int offset = byteList.begin(); - ByteList decoded = decoder.decode(context, byteList, memo + 1 - offset, - p - offset); - result = context.runtime.newString(decoded); - if (result == null) { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } else { - {p = (( p + 1))-1;} - } - } - break; - case 1: -// line 594 "ParserConfig.rl" - { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } - break; -// line 1409 "ParserConfig.java" - } - } - } - -case 2: - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } - if ( ++p != pe ) { - _goto_targ = 1; - continue _goto; - } -case 4: -case 5: - } - break; } - } - -// line 615 "ParserConfig.rl" - - if (cs >= JSON_string_first_final && result != null) { - if (result instanceof RubyString) { - RubyString string = (RubyString)result; - string.setEncoding(UTF8Encoding.INSTANCE); - string.clearCodeRange(); - if (config.freeze) { - string.setFrozen(true); - string = context.runtime.freezeAndDedupString(string); - } - res.update(config.onLoad(context, string), p + 1); - } else { - res.update(config.onLoad(context, result), p + 1); - } - } else { - res.update(null, p + 1); - } - } - - -// line 1450 "ParserConfig.java" -private static byte[] init__JSON_array_actions_0() -{ - return new byte [] { - 0, 1, 0, 1, 1, 1, 2 - }; -} - -private static final byte _JSON_array_actions[] = init__JSON_array_actions_0(); - - -private static byte[] init__JSON_array_cond_offsets_0() -{ - return new byte [] { - 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 6, 6, 6, 6, 6, 8, 11, 16, 19 - }; -} - -private static final byte _JSON_array_cond_offsets[] = init__JSON_array_cond_offsets_0(); - - -private static byte[] init__JSON_array_cond_lengths_0() -{ - return new byte [] { - 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 5, 0, 0, 0, 0, 2, 3, 5, 3, 0 - }; -} - -private static final byte _JSON_array_cond_lengths[] = init__JSON_array_cond_lengths_0(); - - -private static int[] init__JSON_array_cond_keys_0() -{ - return new int [] { - 44, 44, 9, 9, 10, 10, 13, 13, 32, 32, 47, 47, - 42, 42, 47, 47, 0, 41, 42, 42, 43,65535, 0, 41, - 42, 42, 43, 46, 47, 47, 48,65535, 0, 9, 10, 10, - 11,65535, 0 - }; -} - -private static final int _JSON_array_cond_keys[] = init__JSON_array_cond_keys_0(); - - -private static byte[] init__JSON_array_cond_spaces_0() -{ - return new byte [] { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 - }; -} - -private static final byte _JSON_array_cond_spaces[] = init__JSON_array_cond_spaces_0(); - - -private static byte[] init__JSON_array_key_offsets_0() -{ - return new byte [] { - 0, 0, 1, 18, 26, 28, 29, 31, 32, 48, 50, 51, - 53, 54, 76, 78, 79, 81, 82, 86, 92, 100, 106 - }; -} - -private static final byte _JSON_array_key_offsets[] = init__JSON_array_key_offsets_0(); - - -private static int[] init__JSON_array_trans_keys_0() -{ - return new int [] { - 91, 13, 32, 34, 45, 47, 73, 78, 91, 93, 102, 110, - 116, 123, 9, 10, 48, 57, 13, 32, 47, 93,65580,131116, - 9, 10, 42, 47, 42, 42, 47, 10, 13, 32, 34, 45, - 47, 73, 78, 91, 102, 110, 116, 123, 9, 10, 48, 57, - 42, 47, 42, 42, 47, 10, 34, 45, 73, 78, 91, 93, - 102, 110, 116, 123,65549,65568,65583,131085,131104,131119, 48, 57, - 65545,65546,131081,131082, 42, 47, 42, 42, 47, 10,65578,65583, - 131114,131119,65578,131114,65536,131071,131072,196607,65578,65583,131114,131119, - 65536,131071,131072,196607,65546,131082,65536,131071,131072,196607, 0 - }; -} - -private static final int _JSON_array_trans_keys[] = init__JSON_array_trans_keys_0(); - - -private static byte[] init__JSON_array_single_lengths_0() -{ - return new byte [] { - 0, 1, 13, 6, 2, 1, 2, 1, 12, 2, 1, 2, - 1, 16, 2, 1, 2, 1, 4, 2, 4, 2, 0 - }; -} - -private static final byte _JSON_array_single_lengths[] = init__JSON_array_single_lengths_0(); - - -private static byte[] init__JSON_array_range_lengths_0() -{ - return new byte [] { - 0, 0, 2, 1, 0, 0, 0, 0, 2, 0, 0, 0, - 0, 3, 0, 0, 0, 0, 0, 2, 2, 2, 0 - }; -} - -private static final byte _JSON_array_range_lengths[] = init__JSON_array_range_lengths_0(); - - -private static short[] init__JSON_array_index_offsets_0() -{ - return new short [] { - 0, 0, 2, 18, 26, 29, 31, 34, 36, 51, 54, 56, - 59, 61, 81, 84, 86, 89, 91, 96, 101, 108, 113 - }; -} - -private static final short _JSON_array_index_offsets[] = init__JSON_array_index_offsets_0(); - - -private static byte[] init__JSON_array_indicies_0() -{ - return new byte [] { - 0, 1, 0, 0, 2, 2, 3, 2, 2, 2, 4, 2, - 2, 2, 2, 0, 2, 1, 5, 5, 6, 4, 7, 8, - 5, 1, 9, 10, 1, 11, 9, 11, 5, 9, 12, 10, - 7, 7, 2, 2, 13, 2, 2, 2, 2, 2, 2, 2, - 7, 2, 1, 14, 15, 1, 16, 14, 16, 7, 14, 17, - 15, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 0, - 0, 3, 8, 8, 18, 2, 0, 8, 1, 19, 20, 1, - 21, 19, 21, 0, 19, 22, 20, 19, 20, 23, 24, 1, - 21, 25, 19, 23, 1, 21, 0, 25, 8, 19, 23, 1, - 22, 26, 20, 24, 1, 1, 0 - }; -} - -private static final byte _JSON_array_indicies[] = init__JSON_array_indicies_0(); - - -private static byte[] init__JSON_array_trans_targs_0() -{ - return new byte [] { - 2, 0, 3, 14, 22, 3, 4, 8, 13, 5, 7, 6, - 3, 9, 10, 12, 11, 8, 18, 15, 17, 16, 2, 19, - 21, 20, 13 - }; -} - -private static final byte _JSON_array_trans_targs[] = init__JSON_array_trans_targs_0(); - - -private static byte[] init__JSON_array_trans_actions_0() -{ - return new byte [] { - 0, 0, 3, 0, 5, 0, 0, 0, 0, 0, 0, 1, - 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, - 0, 1, 1 - }; -} - -private static final byte _JSON_array_trans_actions[] = init__JSON_array_trans_actions_0(); - - -static final int JSON_array_start = 1; -static final int JSON_array_first_final = 22; -static final int JSON_array_error = 0; - -static final int JSON_array_en_main = 1; - - -// line 669 "ParserConfig.rl" - - - void parseArray(ThreadContext context, ParserResult res, int p, int pe) { - int cs; - - if (config.maxNesting > 0 && currentNesting > config.maxNesting) { - throw newException(context, Utils.M_NESTING_ERROR, - "nesting of " + currentNesting + " is too deep"); - } - - IRubyObject result = RubyArray.newArray(context.runtime); - - -// line 1633 "ParserConfig.java" - { - cs = JSON_array_start; - } - -// line 682 "ParserConfig.rl" - -// line 1640 "ParserConfig.java" - { - int _klen; - int _trans = 0; - int _widec; - int _acts; - int _nacts; - int _keys; - int _goto_targ = 0; - - _goto: while (true) { - switch ( _goto_targ ) { - case 0: - if ( p == pe ) { - _goto_targ = 4; - continue _goto; - } - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } -case 1: - _widec = data[p]; - _keys = _JSON_array_cond_offsets[cs]*2 -; _klen = _JSON_array_cond_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys -; int _mid; - int _upper = _keys + (_klen<<1) - 2; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + (((_upper-_lower) >> 1) & ~1); - if ( _widec < _JSON_array_cond_keys[_mid] ) - _upper = _mid - 2; - else if ( _widec > _JSON_array_cond_keys[_mid+1] ) - _lower = _mid + 2; - else { - switch ( _JSON_array_cond_spaces[_JSON_array_cond_offsets[cs] + ((_mid - _keys)>>1)] ) { - case 0: { - _widec = 65536 + (data[p] - 0); - if ( -// line 640 "ParserConfig.rl" - config.allowTrailingComma ) _widec += 65536; - break; - } - } - break; - } - } - } - - _match: do { - _keys = _JSON_array_key_offsets[cs]; - _trans = _JSON_array_index_offsets[cs]; - _klen = _JSON_array_single_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + _klen - 1; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + ((_upper-_lower) >> 1); - if ( _widec < _JSON_array_trans_keys[_mid] ) - _upper = _mid - 1; - else if ( _widec > _JSON_array_trans_keys[_mid] ) - _lower = _mid + 1; - else { - _trans += (_mid - _keys); - break _match; - } - } - _keys += _klen; - _trans += _klen; - } - - _klen = _JSON_array_range_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + (_klen<<1) - 2; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + (((_upper-_lower) >> 1) & ~1); - if ( _widec < _JSON_array_trans_keys[_mid] ) - _upper = _mid - 2; - else if ( _widec > _JSON_array_trans_keys[_mid+1] ) - _lower = _mid + 2; - else { - _trans += ((_mid - _keys)>>1); - break _match; - } - } - _trans += _klen; - } - } while (false); - - _trans = _JSON_array_indicies[_trans]; - cs = _JSON_array_trans_targs[_trans]; - - if ( _JSON_array_trans_actions[_trans] != 0 ) { - _acts = _JSON_array_trans_actions[_trans]; - _nacts = (int) _JSON_array_actions[_acts++]; - while ( _nacts-- > 0 ) - { - switch ( _JSON_array_actions[_acts++] ) - { - case 0: -// line 321 "ParserConfig.rl" - { - if (!config.allowComments) { - if (config.deprecateComments) { - if (config.deprecateDuplicateKey && emittedDeprecations < 5) { - emittedDeprecations++; - context.runtime.getWarnings().warning( - "Encountered comment in JSON. This will raise an error in json 3.0 unless enabled via `allow_comments: true`" - ); - } - } else { - throw unexpectedToken(context, p, pe); - } - } - } - break; - case 1: -// line 642 "ParserConfig.rl" - { - parseValue(context, res, p, pe); - if (res.result == null) { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } else { - ((RubyArray)result).append(res.result); - {p = (( res.p))-1;} - } - } - break; - case 2: -// line 653 "ParserConfig.rl" - { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } - break; -// line 1789 "ParserConfig.java" - } - } - } - -case 2: - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } - if ( ++p != pe ) { - _goto_targ = 1; - continue _goto; - } -case 4: -case 5: - } - break; } - } - -// line 683 "ParserConfig.rl" - - if (cs >= JSON_array_first_final) { - res.update(config.onLoad(context, result), p + 1); - } else { - throw unexpectedToken(context, p, pe); - } - } - - -// line 1819 "ParserConfig.java" -private static byte[] init__JSON_object_actions_0() -{ - return new byte [] { - 0, 1, 0, 1, 1, 1, 2, 1, 3 - }; -} - -private static final byte _JSON_object_actions[] = init__JSON_object_actions_0(); - - -private static byte[] init__JSON_object_cond_offsets_0() -{ - return new byte [] { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 8, 11, 16, - 19, 19, 19, 19, 19, 19, 19, 19, 19 - }; -} - -private static final byte _JSON_object_cond_offsets[] = init__JSON_object_cond_offsets_0(); - - -private static byte[] init__JSON_object_cond_lengths_0() -{ - return new byte [] { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 5, 3, - 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; -} - -private static final byte _JSON_object_cond_lengths[] = init__JSON_object_cond_lengths_0(); - - -private static int[] init__JSON_object_cond_keys_0() -{ - return new int [] { - 9, 9, 10, 10, 13, 13, 32, 32, 44, 44, 47, 47, - 42, 42, 47, 47, 0, 41, 42, 42, 43,65535, 0, 41, - 42, 42, 43, 46, 47, 47, 48,65535, 0, 9, 10, 10, - 11,65535, 0 - }; -} - -private static final int _JSON_object_cond_keys[] = init__JSON_object_cond_keys_0(); - - -private static byte[] init__JSON_object_cond_spaces_0() -{ - return new byte [] { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 - }; -} - -private static final byte _JSON_object_cond_spaces[] = init__JSON_object_cond_spaces_0(); - - -private static byte[] init__JSON_object_key_offsets_0() -{ - return new byte [] { - 0, 0, 1, 8, 14, 16, 17, 19, 20, 36, 49, 56, - 62, 64, 65, 67, 68, 70, 71, 73, 74, 78, 84, 92, - 98, 100, 101, 103, 104, 106, 107, 109, 110 - }; -} - -private static final byte _JSON_object_key_offsets[] = init__JSON_object_key_offsets_0(); - - -private static int[] init__JSON_object_trans_keys_0() -{ - return new int [] { - 123, 13, 32, 34, 47, 125, 9, 10, 13, 32, 47, 58, - 9, 10, 42, 47, 42, 42, 47, 10, 13, 32, 34, 45, - 47, 73, 78, 91, 102, 110, 116, 123, 9, 10, 48, 57, - 125,65549,65568,65580,65583,131085,131104,131116,131119,65545,65546,131081, - 131082, 13, 32, 44, 47, 125, 9, 10, 13, 32, 34, 47, - 9, 10, 42, 47, 42, 42, 47, 10, 42, 47, 42, 42, - 47, 10,65578,65583,131114,131119,65578,131114,65536,131071,131072,196607, - 65578,65583,131114,131119,65536,131071,131072,196607,65546,131082,65536,131071, - 131072,196607, 42, 47, 42, 42, 47, 10, 42, 47, 42, 42, - 47, 10, 0 - }; -} - -private static final int _JSON_object_trans_keys[] = init__JSON_object_trans_keys_0(); - - -private static byte[] init__JSON_object_single_lengths_0() -{ - return new byte [] { - 0, 1, 5, 4, 2, 1, 2, 1, 12, 9, 5, 4, - 2, 1, 2, 1, 2, 1, 2, 1, 4, 2, 4, 2, - 2, 1, 2, 1, 2, 1, 2, 1, 0 - }; -} - -private static final byte _JSON_object_single_lengths[] = init__JSON_object_single_lengths_0(); - - -private static byte[] init__JSON_object_range_lengths_0() -{ - return new byte [] { - 0, 0, 1, 1, 0, 0, 0, 0, 2, 2, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, - 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; -} - -private static final byte _JSON_object_range_lengths[] = init__JSON_object_range_lengths_0(); - - -private static short[] init__JSON_object_index_offsets_0() -{ - return new short [] { - 0, 0, 2, 9, 15, 18, 20, 23, 25, 40, 52, 59, - 65, 68, 70, 73, 75, 78, 80, 83, 85, 90, 95, 102, - 107, 110, 112, 115, 117, 120, 122, 125, 127 - }; -} - -private static final short _JSON_object_index_offsets[] = init__JSON_object_index_offsets_0(); - - -private static byte[] init__JSON_object_indicies_0() -{ - return new byte [] { - 0, 1, 0, 0, 2, 3, 4, 0, 1, 5, 5, 6, - 7, 5, 1, 8, 9, 1, 10, 8, 10, 5, 8, 11, - 9, 7, 7, 12, 12, 13, 12, 12, 12, 12, 12, 12, - 12, 7, 12, 1, 4, 14, 14, 15, 16, 17, 17, 0, - 18, 14, 17, 1, 14, 14, 15, 16, 4, 14, 1, 15, - 15, 2, 19, 15, 1, 20, 21, 1, 22, 20, 22, 15, - 20, 23, 21, 24, 25, 1, 26, 24, 26, 14, 24, 27, - 25, 24, 25, 28, 29, 1, 26, 30, 24, 28, 1, 26, - 14, 30, 17, 24, 28, 1, 27, 31, 25, 29, 1, 32, - 33, 1, 34, 32, 34, 7, 32, 35, 33, 36, 37, 1, - 38, 36, 38, 0, 36, 39, 37, 1, 0 - }; -} - -private static final byte _JSON_object_indicies[] = init__JSON_object_indicies_0(); - - -private static byte[] init__JSON_object_trans_targs_0() -{ - return new byte [] { - 2, 0, 3, 28, 32, 3, 4, 8, 5, 7, 6, 3, - 9, 24, 10, 11, 16, 9, 20, 12, 13, 15, 14, 11, - 17, 19, 18, 10, 21, 23, 22, 9, 25, 27, 26, 8, - 29, 31, 30, 2 - }; -} - -private static final byte _JSON_object_trans_targs[] = init__JSON_object_trans_targs_0(); - - -private static byte[] init__JSON_object_trans_actions_0() -{ - return new byte [] { - 0, 0, 5, 0, 7, 0, 0, 0, 0, 0, 1, 1, - 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, - 0, 0, 1, 1 - }; -} - -private static final byte _JSON_object_trans_actions[] = init__JSON_object_trans_actions_0(); - - -static final int JSON_object_start = 1; -static final int JSON_object_first_final = 32; -static final int JSON_object_error = 0; - -static final int JSON_object_en_main = 1; - - -// line 754 "ParserConfig.rl" - - - void parseObject(ThreadContext context, ParserResult res, int p, int pe) { - int cs; - IRubyObject lastName = null; - - if (config.maxNesting > 0 && currentNesting > config.maxNesting) { - throw newException(context, Utils.M_NESTING_ERROR, - "nesting of " + currentNesting + " is too deep"); - } - - // this is guaranteed to be a RubyHash due to the earlier - // allocator test at OptionsReader#getClass - IRubyObject result = RubyHash.newHash(context.runtime); - - -// line 2015 "ParserConfig.java" - { - cs = JSON_object_start; - } - -// line 770 "ParserConfig.rl" - -// line 2022 "ParserConfig.java" - { - int _klen; - int _trans = 0; - int _widec; - int _acts; - int _nacts; - int _keys; - int _goto_targ = 0; - - _goto: while (true) { - switch ( _goto_targ ) { - case 0: - if ( p == pe ) { - _goto_targ = 4; - continue _goto; - } - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } -case 1: - _widec = data[p]; - _keys = _JSON_object_cond_offsets[cs]*2 -; _klen = _JSON_object_cond_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys -; int _mid; - int _upper = _keys + (_klen<<1) - 2; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + (((_upper-_lower) >> 1) & ~1); - if ( _widec < _JSON_object_cond_keys[_mid] ) - _upper = _mid - 2; - else if ( _widec > _JSON_object_cond_keys[_mid+1] ) - _lower = _mid + 2; - else { - switch ( _JSON_object_cond_spaces[_JSON_object_cond_offsets[cs] + ((_mid - _keys)>>1)] ) { - case 0: { - _widec = 65536 + (data[p] - 0); - if ( -// line 697 "ParserConfig.rl" - config.allowTrailingComma ) _widec += 65536; - break; - } - } - break; - } - } - } - - _match: do { - _keys = _JSON_object_key_offsets[cs]; - _trans = _JSON_object_index_offsets[cs]; - _klen = _JSON_object_single_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + _klen - 1; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + ((_upper-_lower) >> 1); - if ( _widec < _JSON_object_trans_keys[_mid] ) - _upper = _mid - 1; - else if ( _widec > _JSON_object_trans_keys[_mid] ) - _lower = _mid + 1; - else { - _trans += (_mid - _keys); - break _match; - } - } - _keys += _klen; - _trans += _klen; - } - - _klen = _JSON_object_range_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + (_klen<<1) - 2; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + (((_upper-_lower) >> 1) & ~1); - if ( _widec < _JSON_object_trans_keys[_mid] ) - _upper = _mid - 2; - else if ( _widec > _JSON_object_trans_keys[_mid+1] ) - _lower = _mid + 2; - else { - _trans += ((_mid - _keys)>>1); - break _match; - } - } - _trans += _klen; - } - } while (false); - - _trans = _JSON_object_indicies[_trans]; - cs = _JSON_object_trans_targs[_trans]; - - if ( _JSON_object_trans_actions[_trans] != 0 ) { - _acts = _JSON_object_trans_actions[_trans]; - _nacts = (int) _JSON_object_actions[_acts++]; - while ( _nacts-- > 0 ) - { - switch ( _JSON_object_actions[_acts++] ) - { - case 0: -// line 321 "ParserConfig.rl" - { - if (!config.allowComments) { - if (config.deprecateComments) { - if (config.deprecateDuplicateKey && emittedDeprecations < 5) { - emittedDeprecations++; - context.runtime.getWarnings().warning( - "Encountered comment in JSON. This will raise an error in json 3.0 unless enabled via `allow_comments: true`" - ); - } - } else { - throw unexpectedToken(context, p, pe); - } - } - } - break; - case 1: -// line 699 "ParserConfig.rl" - { - parseValue(context, res, p, pe); - if (res.result == null) { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } else { - ((RubyHash)result).op_aset(context, lastName, res.result); - {p = (( res.p))-1;} - } - } - break; - case 2: -// line 710 "ParserConfig.rl" - { - parseString(context, res, p, pe); - if (res.result == null) { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } else { - RubyString name = (RubyString)res.result; - if (config.symbolizeNames) { - lastName = name.intern(); - } else { - lastName = name; - } - - if (!config.allowDuplicateKey) { - if (((RubyHash)result).hasKey(lastName)) { - if (config.deprecateDuplicateKey && emittedDeprecations < 5) { - emittedDeprecations++; - context.runtime.getWarnings().warning( - "detected duplicate key " + name.inspect() + " in JSON object. This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`" - ); - } else { - throw parsingError(context, "duplicate key" + name.inspect(), p, pe); - } - } - } - - {p = (( res.p))-1;} - } - } - break; - case 3: -// line 740 "ParserConfig.rl" - { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } - break; -// line 2203 "ParserConfig.java" - } - } - } - -case 2: - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } - if ( ++p != pe ) { - _goto_targ = 1; - continue _goto; - } -case 4: -case 5: - } - break; } - } - -// line 771 "ParserConfig.rl" - - if (cs < JSON_object_first_final) { - res.update(null, p + 1); - return; - } - - res.update(config.onLoad(context, result), p + 1); - } - - -// line 2234 "ParserConfig.java" -private static byte[] init__JSON_actions_0() -{ - return new byte [] { - 0, 1, 0, 1, 1 - }; -} - -private static final byte _JSON_actions[] = init__JSON_actions_0(); - - -private static byte[] init__JSON_key_offsets_0() -{ - return new byte [] { - 0, 0, 16, 18, 19, 21, 22, 24, 25, 27, 28 - }; -} - -private static final byte _JSON_key_offsets[] = init__JSON_key_offsets_0(); - - -private static char[] init__JSON_trans_keys_0() -{ - return new char [] { - 13, 32, 34, 45, 47, 73, 78, 91, 102, 110, 116, 123, - 9, 10, 48, 57, 42, 47, 42, 42, 47, 10, 42, 47, - 42, 42, 47, 10, 13, 32, 47, 9, 10, 0 - }; -} - -private static final char _JSON_trans_keys[] = init__JSON_trans_keys_0(); - - -private static byte[] init__JSON_single_lengths_0() -{ - return new byte [] { - 0, 12, 2, 1, 2, 1, 2, 1, 2, 1, 3 - }; -} - -private static final byte _JSON_single_lengths[] = init__JSON_single_lengths_0(); - - -private static byte[] init__JSON_range_lengths_0() -{ - return new byte [] { - 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1 - }; -} - -private static final byte _JSON_range_lengths[] = init__JSON_range_lengths_0(); - - -private static byte[] init__JSON_index_offsets_0() -{ - return new byte [] { - 0, 0, 15, 18, 20, 23, 25, 28, 30, 33, 35 - }; -} - -private static final byte _JSON_index_offsets[] = init__JSON_index_offsets_0(); - - -private static byte[] init__JSON_indicies_0() -{ - return new byte [] { - 0, 0, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, - 0, 2, 1, 4, 5, 1, 6, 4, 6, 7, 4, 8, - 5, 9, 10, 1, 11, 9, 11, 0, 9, 12, 10, 7, - 7, 13, 7, 1, 0 - }; -} - -private static final byte _JSON_indicies[] = init__JSON_indicies_0(); - - -private static byte[] init__JSON_trans_targs_0() -{ - return new byte [] { - 1, 0, 10, 6, 3, 5, 4, 10, 10, 7, 9, 8, - 1, 2 - }; -} - -private static final byte _JSON_trans_targs[] = init__JSON_trans_targs_0(); - - -private static byte[] init__JSON_trans_actions_0() -{ - return new byte [] { - 0, 0, 3, 0, 0, 0, 1, 0, 1, 0, 0, 1, - 1, 0 - }; -} - -private static final byte _JSON_trans_actions[] = init__JSON_trans_actions_0(); - - -static final int JSON_start = 1; -static final int JSON_first_final = 10; -static final int JSON_error = 0; - -static final int JSON_en_main = 1; - - -// line 800 "ParserConfig.rl" - - - public IRubyObject parseImplementation(ThreadContext context) { - int cs; - int p, pe; - IRubyObject result = null; - ParserResult res = new ParserResult(); - - -// line 2349 "ParserConfig.java" - { - cs = JSON_start; - } - -// line 809 "ParserConfig.rl" - p = byteList.begin(); - pe = p + byteList.length(); - -// line 2358 "ParserConfig.java" - { - int _klen; - int _trans = 0; - int _acts; - int _nacts; - int _keys; - int _goto_targ = 0; - - _goto: while (true) { - switch ( _goto_targ ) { - case 0: - if ( p == pe ) { - _goto_targ = 4; - continue _goto; - } - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } -case 1: - _match: do { - _keys = _JSON_key_offsets[cs]; - _trans = _JSON_index_offsets[cs]; - _klen = _JSON_single_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + _klen - 1; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + ((_upper-_lower) >> 1); - if ( data[p] < _JSON_trans_keys[_mid] ) - _upper = _mid - 1; - else if ( data[p] > _JSON_trans_keys[_mid] ) - _lower = _mid + 1; - else { - _trans += (_mid - _keys); - break _match; - } - } - _keys += _klen; - _trans += _klen; - } - - _klen = _JSON_range_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + (_klen<<1) - 2; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + (((_upper-_lower) >> 1) & ~1); - if ( data[p] < _JSON_trans_keys[_mid] ) - _upper = _mid - 2; - else if ( data[p] > _JSON_trans_keys[_mid+1] ) - _lower = _mid + 2; - else { - _trans += ((_mid - _keys)>>1); - break _match; - } - } - _trans += _klen; - } - } while (false); - - _trans = _JSON_indicies[_trans]; - cs = _JSON_trans_targs[_trans]; - - if ( _JSON_trans_actions[_trans] != 0 ) { - _acts = _JSON_trans_actions[_trans]; - _nacts = (int) _JSON_actions[_acts++]; - while ( _nacts-- > 0 ) - { - switch ( _JSON_actions[_acts++] ) - { - case 0: -// line 321 "ParserConfig.rl" - { - if (!config.allowComments) { - if (config.deprecateComments) { - if (config.deprecateDuplicateKey && emittedDeprecations < 5) { - emittedDeprecations++; - context.runtime.getWarnings().warning( - "Encountered comment in JSON. This will raise an error in json 3.0 unless enabled via `allow_comments: true`" - ); - } - } else { - throw unexpectedToken(context, p, pe); - } - } - } - break; - case 1: -// line 786 "ParserConfig.rl" - { - parseValue(context, res, p, pe); - if (res.result == null) { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } else { - result = res.result; - {p = (( res.p))-1;} - } - } - break; -// line 2468 "ParserConfig.java" - } - } - } - -case 2: - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } - if ( ++p != pe ) { - _goto_targ = 1; - continue _goto; - } -case 4: -case 5: - } - break; } - } - -// line 812 "ParserConfig.rl" - - if (cs >= JSON_first_final && p == pe) { - return result; - } else { - throw unexpectedToken(context, p, pe); - } - } - - public IRubyObject parse(ThreadContext context) { - return parseImplementation(context); - } - - /** - * Updates the "view" bytelist with the new offsets and returns it. - * @param absStart - * @param absEnd - */ - private ByteList absSubSequence(int absStart, int absEnd) { - view.setBegin(absStart); - view.setRealSize(absEnd - absStart); - return view; - } - - /** - * Retrieves a constant directly descended from the JSON module. - * @param name The constant name - */ - private IRubyObject getConstant(String name) { - return config.info.jsonModule.get().getConstant(name); - } - - private RaiseException newException(ThreadContext context, String className, String message) { - return Utils.newException(context, className, message); - } - - private RaiseException newException(ThreadContext context, String className, RubyString message) { - return Utils.newException(context, className, message); - } - - RubyHash.VisitorWithState MATCH_VISITOR = new RubyHash.VisitorWithState() { - @Override - public void visit(ThreadContext context, RubyHash self, IRubyObject pattern, IRubyObject klass, int index, IRubyObject[] state) { - if (pattern.callMethod(context, "===", state[0]).isTrue()) { - state[1] = klass; - throw JumpException.SPECIAL_JUMP; - } - } - }; - } -} diff --git a/java/src/json/ext/ParserConfig.rl b/java/src/json/ext/ParserConfig.rl deleted file mode 100644 index e98c71f8..00000000 --- a/java/src/json/ext/ParserConfig.rl +++ /dev/null @@ -1,861 +0,0 @@ -/* - * This code is copyrighted work by Daniel Luz . - * - * Distributed under the Ruby license: https://www.ruby-lang.org/en/about/license.txt - */ -package json.ext; - -import org.jcodings.Encoding; -import org.jcodings.specific.ASCIIEncoding; -import org.jcodings.specific.UTF8Encoding; -import org.jruby.Ruby; -import org.jruby.RubyArray; -import org.jruby.RubyClass; -import org.jruby.RubyFloat; -import org.jruby.RubyHash; -import org.jruby.RubyInteger; -import org.jruby.RubyObject; -import org.jruby.RubyProc; -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.Helpers; -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 java.util.function.BiFunction; - -import static org.jruby.util.ConvertDouble.DoubleConverter; - -/** - * The JSON::Ext::Parser class. - * - *

This is the JSON parser implemented as a Java class. To use it as the - * standard parser, set - *

JSON.parser = JSON::Ext::Parser
- * This is performed for you when you include "json/ext". - * - *

This class does not perform the actual parsing, just acts as an interface - * to Ruby code. When the {@link #parse(ThreadContext)} method is invoked, a - * ParserConfig.ParserSession object is instantiated, which handles the process. - * - * @author mernen - */ -public class ParserConfig extends RubyObject { - private final RuntimeInfo info; - private int maxNesting; - private boolean allowNaN; - private boolean allowTrailingComma; - private boolean allowComments; - private boolean deprecateComments; - private boolean allowControlCharacters; - private boolean allowInvalidEscape; - private boolean allowDuplicateKey; - private boolean deprecateDuplicateKey; - private boolean symbolizeNames; - private boolean freeze; - private RubyProc onLoadProc; - private RubyClass decimalClass; - BiFunction decimalFactory; - private RubyHash match_string; - - private static final int DEFAULT_MAX_NESTING = 100; - - 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 = ParserConfig::new; - - /** - * Multiple-value return for internal parser methods. - * - *

All the parseStuff methods return instances of - * ParserResult when successful, or null when - * there's a problem with the input data. - */ - static final class ParserResult { - /** - * The result of the successful parsing. Should never be - * null. - */ - IRubyObject result; - /** - * The point where the parser returned. - */ - int p; - - void update(IRubyObject result, int p) { - this.result = result; - this.p = p; - } - } - - public ParserConfig(Ruby runtime, RubyClass metaClass) { - super(runtime, metaClass); - info = RuntimeInfo.forRuntime(runtime); - } - - /** - * ParserConfig.new(source, opts = {}) - * - *

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: - *

- *

- *
: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. - *

- *

:allow_trailing_comma - *
If set to true, allow arrays and objects with a trailing - * comma 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. - *

- *

:create_additions - *
If set to false, the Parser doesn't create additions - * even if a matching class and create_id was found. This option - * defaults to true. - *

- *

:object_class - *
Defaults to Hash. If another type is provided, it will be used - * instead of Hash to represent JSON objects. The type must respond to - * new without arguments, and return an object that respond to []=. - *

- *

:array_class - *
Defaults to Array. If another type is provided, it will be used - * instead of Hash to represent JSON arrays. The type must respond to - * new without arguments, and return an object that respond to <<. - *

- *

: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. - *
- */ - - @JRubyMethod(name = "new", meta = true) - public static IRubyObject newInstance(IRubyObject clazz, IRubyObject arg0, Block block) { - ParserConfig config = (ParserConfig)((RubyClass)clazz).allocate(); - - config.callInit(arg0, block); - - return config; - } - - @JRubyMethod(name = "new", meta = true) - public static IRubyObject newInstance(IRubyObject clazz, IRubyObject arg0, IRubyObject arg1, Block block) { - ParserConfig config = (ParserConfig)((RubyClass)clazz).allocate(); - - config.callInit(arg0, arg1, block); - - return config; - } - - @JRubyMethod(visibility = Visibility.PRIVATE) - public IRubyObject initialize(ThreadContext context, IRubyObject options) { - checkFrozen(); - Ruby runtime = context.runtime; - - OptionsReader opts = new OptionsReader(context, options); - this.maxNesting = opts.getInt("max_nesting", DEFAULT_MAX_NESTING); - this.allowNaN = opts.getBool("allow_nan", false); - if (opts.hasKey("allow_comments")) { - this.allowComments = opts.getBool("allow_comments", false); - this.deprecateComments = false; - } else { - this.allowComments = true; - this.deprecateComments = true; - } - - this.allowControlCharacters = opts.getBool("allow_control_characters", false); - this.allowInvalidEscape = opts.getBool("allow_invalid_escape", false); - this.allowTrailingComma = opts.getBool("allow_trailing_comma", false); - this.symbolizeNames = opts.getBool("symbolize_names", false); - if (opts.hasKey("allow_duplicate_key")) { - this.allowDuplicateKey = opts.getBool("allow_duplicate_key", false); - this.deprecateDuplicateKey = false; - } else { - this.allowDuplicateKey = false; - this.deprecateDuplicateKey = true; - } - - this.freeze = opts.getBool("freeze", false); - this.onLoadProc = opts.getProc("on_load"); - - this.decimalClass = opts.getClass("decimal_class", null); - - if (decimalClass == null) { - this.decimalFactory = this::createFloat; - } else if (decimalClass == runtime.getClass("BigDecimal")) { - this.decimalFactory = this::createBigDecimal; - } else { - this.decimalFactory = this::createCustomDecimal; - } - - return this; - } - - public IRubyObject onLoad(ThreadContext context, IRubyObject object) { - if (onLoadProc == null) { - return object; - } else { - return onLoadProc.call(context, object); - } - } - - /** - * 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) { - Encoding encoding = source.getEncoding(); - if (encoding == ASCIIEncoding.INSTANCE) { - source = (RubyString) source.dup(); - source.setEncoding(UTF8Encoding.INSTANCE); - source.clearCodeRange(); - } else if (encoding != UTF8Encoding.INSTANCE) { - source = (RubyString) source.encode(context, context.runtime.getEncodingService().convertEncodingToRubyEncoding(UTF8Encoding.INSTANCE)); - } - return source; - } - - /** - * Parser#parse() - * - *

Parses the current JSON text source and returns the - * complete data structure as a result. - */ - @JRubyMethod - public IRubyObject parse(ThreadContext context, IRubyObject source) { - return new ParserSession(this, convertEncoding(context, source.convertToString()), context, info).parse(context); - } - - /** - * Queries JSON.create_id. Returns null if it is - * set to nil or false, and a String if not. - */ - private RubyString getCreateId(ThreadContext context) { - IRubyObject v = info.jsonModule.get().callMethod(context, "create_id"); - return v.isTrue() ? v.convertToString() : null; - } - - private RubyFloat createFloat(final ThreadContext context, final ByteList num) { - return RubyFloat.newFloat(context.runtime, new DoubleConverter().parse(num, true, true)); - } - - private IRubyObject createBigDecimal(final ThreadContext context, final ByteList num) { - final Ruby runtime = context.runtime; - return runtime.getKernel().callMethod(context, "BigDecimal", runtime.newString(num)); - } - - private IRubyObject createCustomDecimal(final ThreadContext context, final ByteList num) { - return decimalClass.newInstance(context, context.runtime.newString(num), Block.NULL_BLOCK); - } - - /** - * A string parsing session. - * - *

Once a ParserSession is instantiated, the source string should not - * change until the parsing is complete. The ParserSession object assumes - * the source {@link RubyString} is still associated to its original - * {@link ByteList}, which in turn must still be bound to the same - * byte[] value (and on the same offset). - */ - // Ragel uses lots of fall-through - @SuppressWarnings("fallthrough") - private static class ParserSession { - private final ParserConfig config; - private final RuntimeInfo info; - private final ByteList byteList; - private final ByteList view; - private final byte[] data; - private final StringDecoder decoder; - private int currentNesting = 0; - private int emittedDeprecations = 0; - - private ParserSession(ParserConfig config, RubyString source, ThreadContext context, RuntimeInfo info) { - this.config = config; - this.info = info; - this.byteList = source.getByteList(); - this.data = byteList.unsafeBytes(); - this.view = new ByteList(data, false); - this.decoder = new StringDecoder(config.allowControlCharacters, config.allowInvalidEscape); - } - - private RaiseException parsingError(ThreadContext context, String message, int absStart, int absEnd) { - RubyString msg = context.runtime.newString("unexpected token at '") - .cat(data, absStart, Math.min(absEnd - absStart, 32)) - .cat((byte)'\''); - return newException(context, Utils.M_PARSER_ERROR, msg); - } - - private RaiseException unexpectedToken(ThreadContext context, int absStart, int absEnd) { - return parsingError(context, "unexpected token at '", absStart, absEnd); - } - - %%{ - machine JSON_common; - - action parse_comment { - if (!config.allowComments) { - if (config.deprecateComments) { - if (config.deprecateDuplicateKey && emittedDeprecations < 5) { - emittedDeprecations++; - context.runtime.getWarnings().warning( - "Encountered comment in JSON. This will raise an error in json 3.0 unless enabled via `allow_comments: true`" - ); - } - } else { - throw unexpectedToken(context, p, pe); - } - } - } - - cr = '\n'; - cr_neg = [^\n]; - ws = [ \t\r\n]; - c_comment = '/*' ( any* - (any* '*/' any* ) ) '*/' >parse_comment; - cpp_comment = '//' cr_neg* cr >parse_comment; - 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_value; - include JSON_common; - - write data; - - action parse_null { - result = context.nil; - } - action parse_false { - result = context.fals; - } - action parse_true { - result = context.tru; - } - action parse_nan { - if (config.allowNaN) { - result = getConstant(CONST_NAN); - } else { - throw unexpectedToken(context, p - 2, pe); - } - } - action parse_infinity { - if (config.allowNaN) { - result = getConstant(CONST_INFINITY); - } else { - throw unexpectedToken(context, p - 7, pe); - } - } - action parse_number { - if (pe > fpc + 8 && - absSubSequence(fpc, fpc + 9).equals(JSON_MINUS_INFINITY)) { - - if (config.allowNaN) { - result = getConstant(CONST_MINUS_INFINITY); - fexec p + 10; - fhold; - fbreak; - } else { - throw unexpectedToken(context, p, pe); - } - } - parseFloat(context, res, fpc, pe); - if (res.result != null) { - result = res.result; - fexec res.p; - } - parseInteger(context, res, fpc, pe); - if (res.result != null) { - result = res.result; - fexec res.p; - } - fhold; - fbreak; - } - action parse_string { - parseString(context, res, fpc, pe); - if (res.result == null) { - fhold; - fbreak; - } else { - result = res.result; - fexec res.p; - } - } - action parse_array { - currentNesting++; - parseArray(context, res, fpc, pe); - currentNesting--; - if (res.result == null) { - fhold; - fbreak; - } else { - result = res.result; - fexec res.p; - } - } - action parse_object { - currentNesting++; - parseObject(context, res, fpc, pe); - currentNesting--; - if (res.result == null) { - fhold; - fbreak; - } else { - result = res.result; - fexec res.p; - } - } - action exit { - fhold; - 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; - }%% - - void parseValue(ThreadContext context, ParserResult res, int p, int pe) { - int cs; - IRubyObject result = null; - - %% write init; - %% write exec; - - if (cs >= JSON_value_first_final && result != null) { - if (config.freeze) { - result.setFrozen(true); - } - res.update(result, p); - } else { - res.update(null, p); - } - } - - %%{ - machine JSON_integer; - - write data; - - action exit { - fhold; - fbreak; - } - - main := '-'? ( '0' | [1-9][0-9]* ) ( ^[0-9]? @exit ); - }%% - - void parseInteger(ThreadContext context, ParserResult res, int p, int pe) { - int new_p = parseIntegerInternal(p, pe); - if (new_p == -1) { - res.update(null, p); - return; - } - RubyInteger number = createInteger(context, p, new_p); - res.update(config.onLoad(context, number), new_p + 1); - } - - int parseIntegerInternal(int p, int pe) { - int cs; - - %% write init; - int memo = p; - %% write exec; - - if (cs < JSON_integer_first_final) { - return -1; - } - - return p; - } - - RubyInteger createInteger(ThreadContext context, int p, int new_p) { - Ruby runtime = context.runtime; - ByteList num = absSubSequence(p, new_p); - return bytesToInum(runtime, num); - } - - RubyInteger bytesToInum(Ruby runtime, ByteList num) { - return ConvertBytes.byteListToInum(runtime, num, 10, true); - } - - %%{ - machine JSON_float; - include JSON_common; - - write data; - - action exit { - fhold; - fbreak; - } - - main := '-'? - ( ( ( '0' | [1-9][0-9]* ) '.' [0-9]+ ( [Ee] [+\-]?[0-9]+ )? ) - | ( ( '0' | [1-9][0-9]* ) ( [Ee] [+\-]? [0-9]+ ) ) ) - ( ^[0-9Ee.\-]? @exit ); - }%% - - void parseFloat(ThreadContext context, ParserResult res, int p, int pe) { - int new_p = parseFloatInternal(p, pe); - if (new_p == -1) { - res.update(null, p); - return; - } - final ByteList num = absSubSequence(p, new_p); - IRubyObject number = config.decimalFactory.apply(context, num); - - res.update(config.onLoad(context, number), new_p + 1); - } - - int parseFloatInternal(int p, int pe) { - int cs; - - %% write init; - int memo = p; - %% write exec; - - if (cs < JSON_float_first_final) { - return -1; - } - - return p; - } - - %%{ - machine JSON_string; - include JSON_common; - - write data; - - action parse_string { - int offset = byteList.begin(); - ByteList decoded = decoder.decode(context, byteList, memo + 1 - offset, - p - offset); - result = context.runtime.newString(decoded); - if (result == null) { - fhold; - fbreak; - } else { - fexec p + 1; - } - } - - action exit { - fhold; - fbreak; - } - - main := '"' - ( ( ^(["\\]) - | '\\'["\\/bfnrt] - | '\\u'[0-9a-fA-F]{4} - | '\\'^(["\\/bfnrtu]) - )* %parse_string - ) '"' @exit; - }%% - - void parseString(ThreadContext context, ParserResult res, int p, int pe) { - int cs; - IRubyObject result = null; - - %% write init; - int memo = p; - %% write exec; - - if (cs >= JSON_string_first_final && result != null) { - if (result instanceof RubyString) { - RubyString string = (RubyString)result; - string.setEncoding(UTF8Encoding.INSTANCE); - string.clearCodeRange(); - if (config.freeze) { - string.setFrozen(true); - string = context.runtime.freezeAndDedupString(string); - } - res.update(config.onLoad(context, string), p + 1); - } else { - res.update(config.onLoad(context, result), p + 1); - } - } else { - res.update(null, p + 1); - } - } - - %%{ - machine JSON_array; - include JSON_common; - - write data; - - action allow_trailing_comma { config.allowTrailingComma } - - action parse_value { - parseValue(context, res, fpc, pe); - if (res.result == null) { - fhold; - fbreak; - } else { - ((RubyArray)result).append(res.result); - fexec res.p; - } - } - - action exit { - fhold; - fbreak; - } - - next_element = value_separator ignore* begin_value >parse_value; - - main := begin_array - ignore* - ( ( begin_value >parse_value - ignore* ) - ( ignore* - next_element - ignore* )* ( (value_separator ignore*) when allow_trailing_comma )? )? - ignore* - end_array @exit; - }%% - - void parseArray(ThreadContext context, ParserResult res, int p, int pe) { - int cs; - - if (config.maxNesting > 0 && currentNesting > config.maxNesting) { - throw newException(context, Utils.M_NESTING_ERROR, - "nesting of " + currentNesting + " is too deep"); - } - - IRubyObject result = RubyArray.newArray(context.runtime); - - %% write init; - %% write exec; - - if (cs >= JSON_array_first_final) { - res.update(config.onLoad(context, result), p + 1); - } else { - throw unexpectedToken(context, p, pe); - } - } - - %%{ - machine JSON_object; - include JSON_common; - - write data; - - action allow_trailing_comma { config.allowTrailingComma } - - action parse_value { - parseValue(context, res, fpc, pe); - if (res.result == null) { - fhold; - fbreak; - } else { - ((RubyHash)result).op_aset(context, lastName, res.result); - fexec res.p; - } - } - - action parse_name { - parseString(context, res, fpc, pe); - if (res.result == null) { - fhold; - fbreak; - } else { - RubyString name = (RubyString)res.result; - if (config.symbolizeNames) { - lastName = name.intern(); - } else { - lastName = name; - } - - if (!config.allowDuplicateKey) { - if (((RubyHash)result).hasKey(lastName)) { - if (config.deprecateDuplicateKey && emittedDeprecations < 5) { - emittedDeprecations++; - context.runtime.getWarnings().warning( - "detected duplicate key " + name.inspect() + " in JSON object. This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`" - ); - } else { - throw parsingError(context, "duplicate key" + name.inspect(), p, pe); - } - } - } - - fexec res.p; - } - } - - action exit { - fhold; - fbreak; - } - - pair = ignore* begin_name >parse_name ignore* name_separator - ignore* begin_value >parse_value; - next_pair = ignore* value_separator pair; - - main := ( - begin_object - (pair (next_pair)*((ignore* value_separator) when allow_trailing_comma)?)? ignore* - end_object - ) @exit; - }%% - - void parseObject(ThreadContext context, ParserResult res, int p, int pe) { - int cs; - IRubyObject lastName = null; - - if (config.maxNesting > 0 && currentNesting > config.maxNesting) { - throw newException(context, Utils.M_NESTING_ERROR, - "nesting of " + currentNesting + " is too deep"); - } - - // this is guaranteed to be a RubyHash due to the earlier - // allocator test at OptionsReader#getClass - IRubyObject result = RubyHash.newHash(context.runtime); - - %% write init; - %% write exec; - - if (cs < JSON_object_first_final) { - res.update(null, p + 1); - return; - } - - res.update(config.onLoad(context, result), p + 1); - } - - %%{ - machine JSON; - include JSON_common; - - write data; - - action parse_value { - parseValue(context, res, fpc, pe); - if (res.result == null) { - fhold; - fbreak; - } else { - result = res.result; - fexec res.p; - } - } - - main := ignore* - ( begin_value >parse_value) - ignore*; - }%% - - public IRubyObject parseImplementation(ThreadContext context) { - int cs; - int p, pe; - IRubyObject result = null; - ParserResult res = new ParserResult(); - - %% write init; - p = byteList.begin(); - pe = p + byteList.length(); - %% write exec; - - if (cs >= JSON_first_final && p == pe) { - return result; - } else { - throw unexpectedToken(context, p, pe); - } - } - - public IRubyObject parse(ThreadContext context) { - return parseImplementation(context); - } - - /** - * Updates the "view" bytelist with the new offsets and returns it. - * @param absStart - * @param absEnd - */ - private ByteList absSubSequence(int absStart, int absEnd) { - view.setBegin(absStart); - view.setRealSize(absEnd - absStart); - return view; - } - - /** - * Retrieves a constant directly descended from the JSON module. - * @param name The constant name - */ - private IRubyObject getConstant(String name) { - return config.info.jsonModule.get().getConstant(name); - } - - private RaiseException newException(ThreadContext context, String className, String message) { - return Utils.newException(context, className, message); - } - - private RaiseException newException(ThreadContext context, String className, RubyString message) { - return Utils.newException(context, className, message); - } - - RubyHash.VisitorWithState MATCH_VISITOR = new RubyHash.VisitorWithState() { - @Override - public void visit(ThreadContext context, RubyHash self, IRubyObject pattern, IRubyObject klass, int index, IRubyObject[] state) { - if (pattern.callMethod(context, "===", state[0]).isTrue()) { - state[1] = klass; - throw JumpException.SPECIAL_JUMP; - } - } - }; - } -} diff --git a/java/src/json/ext/ParserService.java b/java/src/json/ext/ParserService.java index 88aa9674..2d49bd1a 100644 --- a/java/src/json/ext/ParserService.java +++ b/java/src/json/ext/ParserService.java @@ -27,8 +27,8 @@ public boolean basicLoad(Ruby runtime) throws IOException { RubyModule jsonExtModule = info.jsonModule.get().defineModuleUnder("Ext"); RubyClass parserConfigClass = jsonExtModule.defineClassUnder("ParserConfig", runtime.getObject(), - ParserConfig.ALLOCATOR); - parserConfigClass.defineAnnotatedMethods(ParserConfig.class); + Parser.ALLOCATOR); + parserConfigClass.defineAnnotatedMethods(Parser.class); return true; } } diff --git a/java/src/json/ext/Ryu.java b/java/src/json/ext/Ryu.java new file mode 100644 index 00000000..570cd24c --- /dev/null +++ b/java/src/json/ext/Ryu.java @@ -0,0 +1,770 @@ +// 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. +package json.ext; + +// Note: This is manually translated from ext/json/ext/vendor/ryu.h +public class Ryu { + static final int DOUBLE_POW5_INV_BITCOUNT = 125; + static final int DOUBLE_POW5_BITCOUNT = 125; + static long[][] DOUBLE_POW5_INV_SPLIT = { + { 1L, 2305843009213693952L}, { -7378697629483820646L, 1844674407370955161L}, + { 5165088340638674453L, 1475739525896764129L}, { 7821419487252849886L, 1180591620717411303L}, + { 8824922364862649494L, 1888946593147858085L}, { 7059937891890119595L, 1511157274518286468L}, + { -5420096130713635294L, 1208925819614629174L}, { -8672153809141816470L, 1934281311383406679L}, + { -6937723047313453176L, 1547425049106725343L}, { -1860829623108852217L, 1237940039285380274L}, + { -2977327396974163548L, 1980704062856608439L}, { -2381861917579330838L, 1584563250285286751L}, + { 9162556910162266299L, 1267650600228229401L}, { 7281393426775805432L, 2028240960365167042L}, + { -1553582888063176301L, 1622592768292133633L}, { 2446482504291369283L, 1298074214633706907L}, + { 7603720821608101175L, 2076918743413931051L}, { 2393627842544570617L, 1661534994731144841L}, + { -1774446540706253830L, 1329227995784915872L}, { -6528463279871916451L, 2126764793255865396L}, + { 5845275820328197809L, 1701411834604692317L}, { -2702476973221262399L, 1361129467683753853L}, + { 3054734472329800808L, 2177807148294006166L}, { -1245561236878069677L, 1742245718635204932L}, + { 6382248639981364905L, 1393796574908163946L}, { 2832900194486363201L, 2230074519853062314L}, + { 5955668970331000884L, 1784059615882449851L}, { 1075186361522890384L, 1427247692705959881L}, + { -5658399451047196032L, 2283596308329535809L}, { -4526719560837756825L, 1826877046663628647L}, + { 3757321980813615186L, 1461501637330902918L}, { -8062188859574838821L, 1169201309864722334L}, + { 5547241898389809503L, 1870722095783555735L}, { 4437793518711847602L, 1496577676626844588L}, + { -7517811629256252888L, 1197262141301475670L}, { -960452162584273651L, 1915619426082361072L}, + { 6610335899416401726L, 1532495540865888858L}, { -5779777724692609589L, 1225996432692711086L}, + { -5558295544766265019L, 1961594292308337738L}, { -757287621071101692L, 1569275433846670190L}, + { -4295178911598791677L, 1255420347077336152L}, { 7885109000409574610L, 2008672555323737844L}, + { -8449308058639981605L, 1606938044258990275L}, { 7997948812055656009L, 1285550435407192220L}, + { -5650025974420502002L, 2056880696651507552L}, { 2858676849947419045L, 1645504557321206042L}, + { -5091756149525885410L, 1316403645856964833L}, { -768112209757596011L, 2106245833371143733L}, + { 3074859046935833515L, 1684996666696914987L}, { -4918810391935153834L, 1347997333357531989L}, + { -7870096627096246135L, 2156795733372051183L}, { -2606728486935086585L, 1725436586697640946L}, + { 8982663654677661702L, 1380349269358112757L}, { -385133411483382570L, 2208558830972980411L}, + { -7686804358670526703L, 1766847064778384329L}, { -6149443486936421362L, 1413477651822707463L}, + { -2460411949614453533L, 2261564242916331941L}, { 9099716884534168143L, 1809251394333065553L}, + { -3788272936598396455L, 1447401115466452442L}, { 4348079280205103483L, 1157920892373161954L}, + { -4111119595897565398L, 1852673427797059126L}, { 7779150767507678651L, 1482138742237647301L}, + { 2533971799264232598L, 1185710993790117841L}, { -3324342750661048490L, 1897137590064188545L}, + { -6348823015270749115L, 1517710072051350836L}, { 5988988032009131678L, 1214168057641080669L}, + { -1485665593011120286L, 1942668892225729070L}, { -4877881289150806552L, 1554135113780583256L}, + { 7165741412905085728L, 1243308091024466605L}, { -6981557813061414451L, 1989292945639146568L}, + { -1895897435707221237L, 1591434356511317254L}, { -1516717948565776990L, 1273147485209053803L}, + { 4951948911778577463L, 2037035976334486086L}, { 272210314680951647L, 1629628781067588869L}, + { 3907117066486671641L, 1303703024854071095L}, { 6251387306378674625L, 2085924839766513752L}, + { -2377587784380880946L, 1668739871813211001L}, { 9165976216721026213L, 1334991897450568801L}, + { 7286864317269821294L, 2135987035920910082L}, { -1549206175667963611L, 1708789628736728065L}, + { -4928713755276281212L, 1367031702989382452L}, { 6871453250525591353L, 2187250724783011924L}, + { 9186511415162383406L, 1749800579826409539L}, { -7408186126837734568L, 1399840463861127631L}, + { -8163748988198464986L, 2239744742177804210L}, { 8226396068408869304L, 1791795793742243368L}, + { -4486929589498635526L, 1433436634993794694L}, { -7179087343197816842L, 2293498615990071511L}, + { 5324776569667477496L, 1834798892792057209L}, { 7949170070475892320L, 1467839114233645767L}, + { -1019361573103106790L, 1174271291386916613L}, { 5747719112518849781L, 1878834066219066582L}, + { -2780522339468740821L, 1503067252975253265L}, { -5913766686316902980L, 1202453802380202612L}, + { 5295368560860596524L, 1923926083808324180L}, { 4236294848688477220L, 1539140867046659344L}, + { 7078384693692692099L, 1231312693637327475L}, { -7121328563801244258L, 1970100309819723960L}, + { 9060332407926645887L, 1576080247855779168L}, { -3819780517884414260L, 1260864198284623334L}, + { -6111648828615062817L, 2017382717255397335L}, { -8578667877633960576L, 1613906173804317868L}, + { -3173585487365258138L, 1291124939043454294L}, { -5077736779784413021L, 2065799902469526871L}, + { 7005857020398200553L, 1652639921975621497L}, { -1774012013165260204L, 1322111937580497197L}, + { -6527768035806326650L, 2115379100128795516L}, { 5845832015580669650L, 1692303280103036413L}, + { -6391380831761195250L, 1353842624082429130L}, { 841837113407818570L, 2166148198531886609L}, + { 4362818505468165179L, 1732918558825509287L}, { -3888442825109288503L, 1386334847060407429L}, + { -6221508520174861605L, 2218135755296651887L}, { 2401490813343931363L, 1774508604237321510L}, + { 1921192650675145090L, 1419606883389857208L}, { -615440573661678179L, 2271371013423771532L}, + { 6886345170554478103L, 1817096810739017226L}, { 1819727321701672159L, 1453677448591213781L}, + { -2233566957380572596L, 1162941958872971024L}, { -3573707131808916153L, 1860707134196753639L}, + { -2858965705447132922L, 1488565707357402911L}, { 8780873879868024632L, 1190852565885922329L}, + { 2981351763563108441L, 1905364105417475727L}, { -4993616218633333894L, 1524291284333980581L}, + { 7073153469319063855L, 1219433027467184465L}, { -7129698522799049449L, 1951092843947495144L}, + { -5703758818239239559L, 1560874275157996115L}, { -8252355869333301970L, 1248699420126396892L}, + { 1553625868034358140L, 1997919072202235028L}, { 8621598323911307159L, 1598335257761788022L}, + { -481418970354774919L, 1278668206209430417L}, { -4459619167309550194L, 2045869129935088668L}, + { 121653480894270168L, 1636695303948070935L}, { 97322784715416134L, 1309356243158456748L}, + { -3533632359197244509L, 2094969989053530796L}, { 8241140556867935363L, 1675975991242824637L}, + { -785785183989472356L, 1340780792994259709L}, { -1257256294383155770L, 2145249268790815535L}, + { -4695153850248434939L, 1716199415032652428L}, { -66774265456837628L, 1372959532026121942L}, + { -3796187639472850528L, 2196735251241795108L}, { 652398703163629901L, 1757388200993436087L}, + { -6856778666952916726L, 1405910560794748869L}, { 7475898206584884855L, 2249456897271598191L}, + { 2291369750525997561L, 1799565517817278553L}, { 9211793429904618695L, 1439652414253822842L}, + { -18525771120251381L, 2303443862806116547L}, { 7363877012587619542L, 1842755090244893238L}, + { -5176944834155635336L, 1474204072195914590L}, { -7830904682066418592L, 1179363257756731672L}, + { 2227947767661371545L, 1886981212410770676L}, { -1906990600612813087L, 1509584969928616540L}, + { -5214941295232160793L, 1207667975942893232L}, { 6413489186596184024L, 1932268761508629172L}, + { -2247906280206873427L, 1545815009206903337L}, { 5580372605318321905L, 1236652007365522670L}, + { 8928596168509315048L, 1978643211784836272L}, { -235820694676368608L, 1582914569427869017L}, + { 7190041073742725760L, 1266331655542295214L}, { 436019273762630246L, 2026130648867672343L}, + { 7727513048493924843L, 1620904519094137874L}, { -8575384820172501418L, 1296723615275310299L}, + { 4726128361433549347L, 2074757784440496479L}, { 7470251503888749801L, 1659806227552397183L}, + { -5091845241114731129L, 1327844982041917746L}, { -4457603571041659483L, 2124551971267068394L}, + { -3566082856833327587L, 1699641577013654715L}, { -6542215100208572392L, 1359713261610923772L}, + { 4289851098633925465L, 2175541218577478036L}, { -257467935834769951L, 1740432974861982428L}, + { 3483374466074094362L, 1392346379889585943L}, { 1884050330976640656L, 2227754207823337509L}, + { 5196589079523222848L, 1782203366258670007L}, { -3221426365865242368L, 1425762693006936005L}, + { 5913764258841343181L, 2281220308811097609L}, { 8420360221814984868L, 1824976247048878087L}, + { -642409452031832752L, 1459980997639102469L}, { -513927561625466201L, 1167984798111281975L}, + { -8200981728084566569L, 1868775676978051161L}, { 4507261061758077715L, 1495020541582440929L}, + { 7295157664148372495L, 1196016433265952743L}, { 7982903447895485668L, 1913626293225524389L}, + { -8371072500651252758L, 1530901034580419511L}, { 4371188443704728763L, 1224720827664335609L}, + { -4074144934298164949L, 1959553324262936974L}, { -3259315947438531959L, 1567642659410349579L}, + { -2607452757950825567L, 1254114127528279663L}, { 3206773216762499739L, 2006582604045247462L}, + { -4813279056073820855L, 1605266083236197969L}, { -3850623244859056684L, 1284212866588958375L}, + { 4907049252451240275L, 2054740586542333401L}, { 236290587219081897L, 1643792469233866721L}, + { -3500316344966644806L, 1315033975387093376L}, { -1911157337204721366L, 2104054360619349402L}, + { 5849771759720043554L, 1683243488495479522L}, { -2698880221707785803L, 1346594790796383617L}, + { -8007557169474367609L, 2154551665274213788L}, { -2716696920837583764L, 1723641332219371030L}, + { -5862706351411977334L, 1378913065775496824L}, { 9066413911450387881L, 2206260905240794919L}, + { -7504264129807330988L, 1765008724192635935L}, { 8753983955121776503L, 1412006979354108748L}, + { -8129718560256619535L, 2259211166966573997L}, { 874922781278525018L, 1807368933573259198L}, + { 8078635854506640661L, 1445895146858607358L}, { -4605137760620418441L, 1156716117486885886L}, + { -3678871602250759182L, 1850745787979017418L}, { 746251532941302978L, 1480596630383213935L}, + { 597001226353042382L, 1184477304306571148L}, { -2734146852577042512L, 1895163686890513836L}, + { 8880728962164096960L, 1516130949512411069L}, { -7652812089236363725L, 1212904759609928855L}, + { -1176452898552450990L, 1940647615375886168L}, { 2748186495899949531L, 1552518092300708935L}, + { 2198549196719959625L, 1242014473840567148L}, { -171670099989974923L, 1987223158144907436L}, + { -7516033709475800585L, 1589778526515925949L}, { -6012826967580640468L, 1271822821212740759L}, + { 8826220925580526867L, 2034916513940385215L}, { 7060976740464421494L, 1627933211152308172L}, + { -1729916237112283451L, 1302346568921846537L}, { -6457214794121563846L, 2083754510274954460L}, + { -8855120650039161400L, 1667003608219963568L}, { -3394747705289418796L, 1333602886575970854L}, + { -5431596328463070074L, 2133764618521553367L}, { 3033420566713364587L, 1707011694817242694L}, + { 6116085268112601993L, 1365609355853794155L}, { -8661007644729388428L, 2184974969366070648L}, + { -3239457301041600419L, 1747979975492856518L}, { 1097782973908629988L, 1398383980394285215L}, + { 1756452758253807981L, 2237414368630856344L}, { 5094511021344956708L, 1789931494904685075L}, + { 4075608817075965366L, 1431945195923748060L}, { 6520974107321544586L, 2291112313477996896L}, + { 1527430471115325346L, 1832889850782397517L}, { -6156753252591560370L, 1466311880625918013L}, + { -1236053787331337972L, 1173049504500734410L}, { 9090360384495590213L, 1876879207201175057L}, + { -106409321887348476L, 1501503365760940045L}, { -3774476272251789104L, 1201202692608752036L}, + { -2349813220860952243L, 1921924308174003258L}, { 1809498238053148529L, 1537539446539202607L}, + { -5931099039041301823L, 1230031557231362085L}, { 1578287981759648052L, 1968050491570179337L}, + { -6116067244076102204L, 1574440393256143469L}, { -4892853795260881763L, 1259552314604914775L}, + { 3239480371808320148L, 2015283703367863641L}, { -1097764517295254205L, 1612226962694290912L}, + { 6500486015647617283L, 1289781570155432730L}, { -8045966448673363964L, 2063650512248692368L}, + { -2747424344196780848L, 1650920409798953894L}, { -2197939475357424678L, 1320736327839163115L}, + { 7551343283653851484L, 2113178124542660985L}, { 6041074626923081187L, 1690542499634128788L}, + { -6235186742687266020L, 1352433999707303030L}, { 1091747655926105338L, 2163894399531684849L}, + { 4562746939482794594L, 1731115519625347879L}, { 7339546366328145998L, 1384892415700278303L}, + { 8053925371383123274L, 2215827865120445285L}, { 6443140297106498619L, 1772662292096356228L}, + { -5913534206540532074L, 1418129833677084982L}, { 5295740528502789974L, 2269007733883335972L}, + { -3142105206681588667L, 1815206187106668777L}, { 4865013464138549713L, 1452164949685335022L}, + { -3486686858172980876L, 1161731959748268017L}, { 9178696285890871890L, 1858771135597228828L}, + { -3725089415513033457L, 1487016908477783062L}, { 4398626097073393881L, 1189613526782226450L}, + { 7037801755317430209L, 1903381642851562320L}, { 5630241404253944167L, 1522705314281249856L}, + { 814844308661245011L, 1218164251424999885L}, { 1303750893857992017L, 1949062802279999816L}, + { -2646348099655516710L, 1559250241823999852L}, { 5261619149759407279L, 1247400193459199882L}, + { -6338804619352589647L, 1995840309534719811L}, { 5997002748743659252L, 1596672247627775849L}, + { 8486951013736837725L, 1277337798102220679L}, { 2511075177753209390L, 2043740476963553087L}, + { -5369837487281253134L, 1634992381570842469L}, { -4295869989825002507L, 1307993905256673975L}, + { 4194654460505726958L, 2092790248410678361L}, { -333625246337328757L, 1674232198728542688L}, + { 3422448617672047318L, 1339385758982834151L}, { -1902779841208544938L, 2143017214372534641L}, + { -8900921502450656597L, 1714413771498027713L}, { -3431388387218614954L, 1371531017198422170L}, + { 5577825024675947042L, 2194449627517475473L}, { -6605786424484973336L, 1755559702013980378L}, + { -1595280324846068345L, 1404447761611184302L}, { -6241797334495619676L, 2247116418577894884L}, + { -4993437867596495741L, 1797693134862315907L}, { 3383947335406624054L, 1438154507889852726L}, + { -1964381892833222160L, 2301047212623764361L}, { -8950203143750398374L, 1840837770099011489L}, + { -7160162515000318699L, 1472670216079209191L}, { 5339916432225476010L, 1178136172863367353L}, + { 4854517476818851293L, 1885017876581387765L}, { 3883613981455081034L, 1508014301265110212L}, + { -4271806444319755819L, 1206411441012088169L}, { -6834890310911609310L, 1930258305619341071L}, + { 5600134195496443521L, 1544206644495472857L}, { -2898590273086665829L, 1235365315596378285L}, + { 6430302007287065643L, 1976584504954205257L}, { -2234456023654168132L, 1581267603963364205L}, + { -5476913633665244829L, 1265014083170691364L}, { -8763061813864391727L, 2024022533073106183L}, + { -3321100636349603058L, 1619218026458484946L}, { 8411165935146048523L, 1295374421166787957L}, + { -1299529762733963656L, 2072599073866860731L}, { -8418321439670991571L, 1658079259093488585L}, + { 8022738107230848036L, 1326463407274790868L}, { 9147032156827446534L, 2122341451639665389L}, + { -7439769533505684065L, 1697873161311732311L}, { 5116230817421183718L, 1358298529049385849L}, + { -2882077136351837022L, 2173277646479017358L}, { 1383687105660440706L, 1738622117183213887L}, + { -6271747944955468082L, 1390897693746571109L}, { 8411947361780802685L, 2225436309994513775L}, + { 6729557889424642148L, 1780349047995611020L}, { 5383646311539713719L, 1424279238396488816L}, + { 1235136468979721303L, 2278846781434382106L}, { -2701239639558133281L, 1823077425147505684L}, + { -2160991711646506624L, 1458461940118004547L}, { 5649904260166615347L, 1166769552094403638L}, + { 5350498001524674232L, 1866831283351045821L}, { 591049586477829062L, 1493465026680836657L}, + { -6905857960301557397L, 1194772021344669325L}, { 18673707743239135L, 1911635234151470921L}, + { -3674409848547319015L, 1529308187321176736L}, { 8128518565387875758L, 1223446549856941389L}, + { 1937583260394870242L, 1957514479771106223L}, { 8928764237799716840L, 1566011583816884978L}, + { -3925035053985957497L, 1252809267053507982L}, { 8477339172590109297L, 2004494827285612772L}, + { -596826291411733209L, 1603595861828490217L}, { 6901236596354434079L, 1282876689462792174L}, + { -26067890058636443L, 2052602703140467478L}, { 3668494502695001169L, 1642082162512373983L}, + { -8133250842069730034L, 1313665730009899186L}, { 9122891541139893884L, 2101865168015838698L}, + { -3769733211313815862L, 1681492134412670958L}, { 673562245690857633L, 1345193707530136767L}, + }; + static long[][] DOUBLE_POW5_SPLIT = { + { 0L, 1152921504606846976L}, { 0L, 1441151880758558720L}, + { 0L, 1801439850948198400L}, { 0L, 2251799813685248000L}, + { 0L, 1407374883553280000L}, { 0L, 1759218604441600000L}, + { 0L, 2199023255552000000L}, { 0L, 1374389534720000000L}, + { 0L, 1717986918400000000L}, { 0L, 2147483648000000000L}, + { 0L, 1342177280000000000L}, { 0L, 1677721600000000000L}, + { 0L, 2097152000000000000L}, { 0L, 1310720000000000000L}, + { 0L, 1638400000000000000L}, { 0L, 2048000000000000000L}, + { 0L, 1280000000000000000L}, { 0L, 1600000000000000000L}, + { 0L, 2000000000000000000L}, { 0L, 1250000000000000000L}, + { 0L, 1562500000000000000L}, { 0L, 1953125000000000000L}, + { 0L, 1220703125000000000L}, { 0L, 1525878906250000000L}, + { 0L, 1907348632812500000L}, { 0L, 1192092895507812500L}, + { 0L, 1490116119384765625L}, { 4611686018427387904L, 1862645149230957031L}, + { -8646911284551352320L, 1164153218269348144L}, { -6196953087261802496L, 1455191522836685180L}, + { -3134505340649865216L, 1818989403545856475L}, { -3918131675812331520L, 2273736754432320594L}, + { -4754675306596401152L, 1421085471520200371L}, { -5943344133245501440L, 1776356839400250464L}, + { -2817494148129488896L, 2220446049250313080L}, { -8678462870222012416L, 1387778780781445675L}, + { 7598665485932036096L, 1734723475976807094L}, { 274959820560269312L, 2168404344971008868L}, + { -9051522149004607488L, 1355252715606880542L}, { 2520655369026404352L, 1694065894508600678L}, + { -6072552825571770368L, 2117582368135750847L}, { -3795345515982356480L, 1323488980084844279L}, + { -4744181894977945600L, 1654361225106055349L}, { 3293144668132343808L, 2067951531382569187L}, + { -247627591630979072L, 1292469707114105741L}, { 8913837547316051968L, 1615587133892632177L}, + { -2692761121137098752L, 2019483917365790221L}, { -6294661719138074624L, 1262177448353618888L}, + { -3256641130495205376L, 1577721810442023610L}, { -8682487431546394624L, 1972152263052529513L}, + { -814868626289108736L, 1232595164407830945L}, { 8204786253993389888L, 1540743955509788682L}, + { 1032610780636961552L, 1925929944387235853L}, { 2951224747111794922L, 1203706215242022408L}, + { 3689030933889743652L, 1504632769052528010L}, { -4612083369492596243L, 1880790961315660012L}, + { -576709096719178700L, 1175494350822287507L}, { -720886370898973375L, 1469367938527859384L}, + { 3710578054803671186L, 1836709923159824231L}, { 26536550077201078L, 2295887403949780289L}, + { -6900943683842831182L, 1434929627468612680L}, { -4014493586376151074L, 1793662034335765850L}, + { 8816941072311974870L, 2242077542919707313L}, { -1406940857446097563L, 1401298464324817070L}, + { -6370362090235009857L, 1751623080406021338L}, { 5872105442488401391L, 2189528850507526673L}, + { -3247463126085830987L, 1368455531567204170L}, { -8671014926034676638L, 1710569414459005213L}, + { -1615396620688569989L, 2138211768073756516L}, { 1296220121283337709L, 1336382355046097823L}, + { -2991410866823215768L, 1670477943807622278L}, { -8350949601956407614L, 2088097429759527848L}, + { 6309871544845715001L, 1305060893599704905L}, { -5947718624225019960L, 1631326116999631131L}, + { -7434648280281274950L, 2039157646249538914L}, { -6952498184389490796L, 1274473528905961821L}, + { 532749306367912313L, 1593091911132452277L}, { 5277622651387278295L, 1991364888915565346L}, + { 7910200175544436838L, 1244603055572228341L}, { -3947307835851617664L, 1555753819465285426L}, + { 8900923260467641632L, 1944692274331606783L}, { -5966138008276193740L, 1215432671457254239L}, + { -7457672510345242175L, 1519290839321567799L}, { 9124653435777998898L, 1899113549151959749L}, + { 8008751406574943263L, 1186945968219974843L}, { 5399253239791291175L, 1483682460274968554L}, + { -2474305487115661840L, 1854603075343710692L}, { 759402079766405302L, 1159126922089819183L}, + { -3662433418719381276L, 1448908652612273978L}, { -9189727791826614499L, 1811135815765342473L}, + { -2263787702928492316L, 2263919769706678091L}, { 7808504722524468110L, 1414949856066673807L}, + { 5148944884728197234L, 1768687320083342259L}, { 1824495087482858639L, 2210859150104177824L}, + { 1140309429676786649L, 1381786968815111140L}, { 1425386787095983311L, 1727233711018888925L}, + { 6393419502297367043L, 2159042138773611156L}, { -5227484847918921406L, 1349401336733506972L}, + { -1922670041471263854L, 1686751670916883715L}, { -2403337551839079817L, 2108439588646104644L}, + { 803757039314269066L, 1317774742903815403L}, { -3606989719284551571L, 1647218428629769253L}, + { 4714634887749086344L, 2059023035787211567L}, { -8582568241225290795L, 1286889397367007229L}, + { -1504838264676837686L, 1608611746708759036L}, { 2730638187581340797L, 2010764683385948796L}, + { -7516723169616437810L, 1256727927116217997L}, { -172531925165771454L, 1570909908895272496L}, + { 4396021111970173586L, 1963637386119090621L}, { 5053356204195052443L, 1227273366324431638L}, + { -2906676781610960254L, 1534091707905539547L}, { -3633345977013700317L, 1917614634881924434L}, + { -4576684244847256650L, 1198509146801202771L}, { -5720855306059070813L, 1498136433501503464L}, + { -2539383114146450612L, 1872670541876879330L}, { -3892957455555225585L, 1170419088673049581L}, + { 4357175217410743827L, 1463023860841311977L}, { -8388589033518733928L, 1828779826051639971L}, + { 7961007781811134206L, 2285974782564549964L}, { -4247742173222816929L, 1428734239102843727L}, + { -5309677716528521161L, 1785917798878554659L}, { -6637097145660651452L, 2232397248598193324L}, + { -1842342706824213205L, 1395248280373870827L}, { -2302928383530266507L, 1744060350467338534L}, + { -7490346497840221037L, 2180075438084173168L}, { 6847748484918331612L, 1362547148802608230L}, + { -663686430706861293L, 1703183936003260287L}, { -829608038383576617L, 2128979920004075359L}, + { -518505023989735386L, 1330612450002547099L}, { -648131279987169232L, 1663265562503183874L}, + { -5421850118411349444L, 2079081953128979843L}, { 5834715712847682405L, 1299426220705612402L}, + { -1929977395795172801L, 1624282775882015502L}, { -7024157763171353905L, 2030353469852519378L}, + { -6695941611195790143L, 1268970918657824611L}, { -8369927013994737679L, 1586213648322280764L}, + { -5850722749066034194L, 1982767060402850955L}, { 5566670318688504437L, 1239229412751781847L}, + { 2346651879933242642L, 1549036765939727309L}, { 7545000868343941206L, 1936295957424659136L}, + { 4715625542714963254L, 1210184973390411960L}, { 5894531928393704067L, 1512731216738014950L}, + { -1855207126362645724L, 1890914020922518687L}, { -1159504453976653577L, 1181821263076574179L}, + { -1449380567470816972L, 1477276578845717724L}, { 2799960309088866689L, 1846595723557147156L}, + { -7473396843674234127L, 1154122327223216972L}, { -4730060036165404755L, 1442652909029021215L}, + { -5912575045206755944L, 1803316136286276519L}, { -7390718806508444929L, 2254145170357845649L}, + { -7513235640390177L, 1408840731473653530L}, { -4621077562977875625L, 1761050914342066913L}, + { 3447025083132431277L, 2201313642927583642L}, { 6766076695385157452L, 1375821026829739776L}, + { 8457595869231446815L, 1719776283537174720L}, { -7874749237170243097L, 2149720354421468400L}, + { 6607496772837067824L, 1343575221513417750L}, { -964001070808441028L, 1679469026891772187L}, + { -1205001338510551285L, 2099336283614715234L}, { -3058968845782788505L, 1312085177259197021L}, + { 5399660979626290177L, 1640106471573996277L}, { -7085481830749300991L, 2050133089467495346L}, + { -6734269153432007072L, 1281333180917184591L}, { -8417836441790008839L, 1601666476146480739L}, + { 7924448521472040567L, 2002083095183100924L}, { -4270591710934750454L, 1251301934489438077L}, + { 3885132398186337741L, 1564127418111797597L}, { -8978642557549241536L, 1955159272639746996L}, + { -3305808589254582008L, 1221974545399841872L}, { 479425281859160394L, 1527468181749802341L}, + { 5210967620751338397L, 1909335227187252926L}, { -1354831255457801406L, 1193334516992033078L}, + { -6305225087749639662L, 1491668146240041348L}, { -3269845341259661673L, 1864585182800051685L}, + { -6655339356714676450L, 1165365739250032303L}, { -8319174195893345562L, 1456707174062540379L}, + { 8047776328842869663L, 1820883967578175474L}, { 836348374198811271L, 2276104959472719343L}, + { 7440246761515338900L, 1422565599670449589L}, { -4534749603387990086L, 1778206999588061986L}, + { 8166621051047176104L, 2222758749485077483L}, { 2798295147690791113L, 1389224218428173427L}, + { -1113817083813899013L, 1736530273035216783L}, { -1392271354767373766L, 2170662841294020979L}, + { 8353202440125167204L, 1356664275808763112L}, { -8005241023553092611L, 1695830344760953890L}, + { 3828506775840797949L, 2119787930951192363L}, { 86973725686804766L, 1324867456844495227L}, + { -4502968861318881947L, 1656084321055619033L}, { 3594660960206173375L, 2070105401319523792L}, + { 2246663100128858359L, 1293815875824702370L}, { -6415043161693702859L, 1617269844780877962L}, + { 5816254103165035138L, 2021587305976097453L}, { 5941001823691840913L, 1263492066235060908L}, + { 7426252279614801142L, 1579365082793826135L}, { 4671129331091113523L, 1974206353492282669L}, + { 5225298841145639904L, 1233878970932676668L}, { 6531623551432049880L, 1542348713665845835L}, + { 3552843420862674446L, 1927935892082307294L}, { -2391158880388216375L, 1204959932551442058L}, + { -7600634618912658373L, 1506199915689302573L}, { -277421236786047158L, 1882749894611628216L}, + { -7090917300632361330L, 1176718684132267635L}, { -8863646625790451662L, 1470898355165334544L}, + { -6467872263810676674L, 1838622943956668180L}, { -3473154311335957938L, 2298278679945835225L}, + { 2440964573842414192L, 1436424174966147016L}, { 3051205717303017741L, 1795530218707683770L}, + { -5409364890226003632L, 2244412773384604712L}, { 8148361989677217490L, 1402757983365377945L}, + { -3649605568185641850L, 1753447479206722431L}, { -4562006960232052312L, 2191809349008403039L}, + { -2851254350145032695L, 1369880843130251899L}, { -3564067937681290869L, 1712351053912814874L}, + { -9066770940529001490L, 2140438817391018593L}, { -1055045819403238027L, 1337774260869386620L}, + { 3292878744173340370L, 1672217826086733276L}, { 4116098430216675462L, 2090272282608416595L}, + { 266718509671728212L, 1306420176630260372L}, { 333398137089660265L, 1633025220787825465L}, + { 5028433689789463235L, 2041281525984781831L}, { -8386443989950055238L, 1275800953740488644L}, + { -5871368969010181144L, 1594751192175610805L}, { 1884160825592049379L, 1993438990219513507L}, + { -1128242493218663091L, 1245899368887195941L}, { 7813068920331446945L, 1557374211108994927L}, + { 5154650131986920777L, 1946717763886243659L}, { 915813323278131534L, 1216698602428902287L}, + { -3466919364329723487L, 1520873253036127858L}, { -8945335223839542262L, 1901091566295159823L}, + { -5590834514899713914L, 1188182228934474889L}, { 2234828893230133415L, 1485227786168093612L}, + { 2793536116537666769L, 1856534732710117015L}, { 8663489100477123587L, 1160334207943823134L}, + { 1605989338741628675L, 1450417759929778918L}, { -7215885363427739964L, 1813022199912223647L}, + { -9019856704284674954L, 2266277749890279559L}, { -5637410440177921847L, 1416423593681424724L}, + { -2435077031795014404L, 1770529492101780905L}, { 6179525747111007803L, 2213161865127226132L}, + { -5361168444910395931L, 1383226165704516332L}, { -2089774537710607010L, 1729032707130645415L}, + { -2612218172138258762L, 2161290883913306769L}, { 2979049660840976177L, 1350806802445816731L}, + { -887873942376167682L, 1688508503057270913L}, { 8113529608884566205L, 2110635628821588642L}, + { -8764102049729309834L, 1319147268013492901L}, { -1731755525306861484L, 1648934085016866126L}, + { -6776380425060964759L, 2061167606271082658L}, { -6541080774876796927L, 1288229753919426661L}, + { 1047021068258779650L, 1610287192399283327L}, { -3302909683103913342L, 2012858990499104158L}, + { 4853210475701136017L, 1258036869061940099L}, { 1454827076199032118L, 1572546086327425124L}, + { 1818533845248790147L, 1965682607909281405L}, { 3442426662494187794L, 1228551629943300878L}, + { -4920338708737041066L, 1535689537429126097L}, { 3072948650933474476L, 1919611921786407622L}, + { -2691093111593966357L, 1199757451116504763L}, { -3363866389492457946L, 1499696813895630954L}, + { -8816519005292960336L, 1874621017369538693L}, { 8324733676974063502L, 1171638135855961683L}, + { 5794231077790191473L, 1464547669819952104L}, { 7242788847237739342L, 1830684587274940130L}, + { -169885977807601630L, 2288355734093675162L}, { -2412021745343444971L, 1430222333808546976L}, + { 1596658836748081690L, 1787777917260683721L}, { 6607509564362490017L, 2234722396575854651L}, + { 1823850468512862308L, 1396701497859909157L}, { 6891499104068465790L, 1745876872324886446L}, + { -608998156769193571L, 2182346090406108057L}, { 4231062170446641922L, 1363966306503817536L}, + { 5288827713058302403L, 1704957883129771920L}, { 6611034641322878003L, 2131197353912214900L}, + { -5091475386027977056L, 1331998346195134312L}, { -1752658214107583416L, 1664997932743917890L}, + { -6802508786061867174L, 2081247415929897363L}, { 4971804045566108824L, 1300779634956185852L}, + { 6214755056957636030L, 1625974543695232315L}, { 3156757802769657134L, 2032468179619040394L}, + { 6584659645158423613L, 1270292612261900246L}, { -992547480406746292L, 1587865765327375307L}, + { -1240684350508432865L, 1984832206659219134L}, { 6142101308573311315L, 1240520129162011959L}, + { 3065940617289251240L, 1550650161452514949L}, { 8444111790038951954L, 1938312701815643686L}, + { 665883850346957067L, 1211445438634777304L}, { 832354812933696334L, 1514306798293471630L}, + { -8182928520687655390L, 1892883497866839537L}, { -502644307002396715L, 1183052186166774710L}, + { -5239991402180383798L, 1478815232708468388L}, { -1938303234298091843L, 1848519040885585485L}, + { -5823125539863695306L, 1155324400553490928L}, { -2667220906402231229L, 1444155500691863660L}, + { 1277659885424598868L, 1805194375864829576L}, { 1597074856780748586L, 2256492969831036970L}, + { 5609857803915355770L, 1410308106144398106L}, { -2211049781960581095L, 1762885132680497632L}, + { 1847873790976661535L, 2203606415850622041L}, { -5762607908280668397L, 1377254009906638775L}, + { -7203259885350835496L, 1721567512383298469L}, { 219297180166231438L, 2151959390479123087L}, + { 7054589765244976505L, 1344974619049451929L}, { -5016820848725943081L, 1681218273811814911L}, + { -6271026060907428851L, 2101522842264768639L}, { -3919391288067143032L, 1313451776415480399L}, + { -4899239110083928790L, 1641814720519350499L}, { -6124048887604910988L, 2052268400649188124L}, + { -1521687545539375415L, 1282667750405742577L}, { 7321262604930556539L, 1603334688007178222L}, + { -71793780691580134L, 2004168360008972777L}, { 4566814905495150320L, 1252605225005607986L}, + { -3514853404985837908L, 1565756531257009982L}, { -9005252774659685289L, 1957195664071262478L}, + { 1289246043478778550L, 1223247290044539049L}, { 6223243572775861092L, 1529059112555673811L}, + { 3167368447542438461L, 1911323890694592264L}, { 1979605279714024038L, 1194577431684120165L}, + { 7086192618069917952L, 1493221789605150206L}, { -365631264267378368L, 1866527237006437757L}, + { -4840205558594499384L, 1166579523129023598L}, { 7784801107039039482L, 1458224403911279498L}, + { 507629346944023544L, 1822780504889099373L}, { 5246222702107417334L, 2278475631111374216L}, + { 3278889188817135834L, 1424047269444608885L}, { 8710297504448807696L, 1780059086805761106L}, + }; + + // IEEE 754 double precision constants + static final int DOUBLE_MANTISSA_BITS = 52; + static final int DOUBLE_EXPONENT_BITS = 11; + static final int DOUBLE_EXPONENT_BIAS = 1023; + + // Helper: floor(log2(value)) using ryu_leading_zeros64 + static int floorLog2(long value) { + return 63 - Long.numberOfLeadingZeros(value); + } + + // Helper: log2(5^e) approximation + static int log2pow5(int e) { + return (e * 1217359) >> 19; + } + + // Helper: ceil(log2(5^e)) + static int ceilLog2pow5(int e) { + return log2pow5(e) + 1; + } + + // Helper: convert uint64 bits to double + static double int64Bits2Double(long bits) { + return Double.longBitsToDouble(bits); + } + + // Check if value is multiple of 2^p + static boolean multipleOfPowerOf2(long value) { + return (value & (value - 1)) == 0; + } + + // Count how many times value is divisible by 5 + // Uses modular inverse to avoid expensive division + static int pow5Factor(long value) { + final long mInv5 = -3689348814741910323L; + final long nDiv5 = 3689348814741910323L; + int count = 0; + for (;;) { + value *= mInv5; + if (Long.compareUnsigned(value, nDiv5) > 0) { + break; + } + ++count; + } + return count; + } + + // Check if value is multiple of 5^p + // Optimized: uses modular inverse instead of division + static boolean multipleOfPowerOf5(long value, int p) { + return pow5Factor(value) >= p; + } + + // Fallback for systems without 128-bit integers + static long umul128(long a, long b, long[] productHi) { + long aLo = Integer.toUnsignedLong((int)a); + long aHi = Integer.toUnsignedLong((int)(a >> 32)); + long bLo = Integer.toUnsignedLong((int)b); + long bHi = Integer.toUnsignedLong((int)(b >> 32)); + + long b00 = aLo * bLo; + long b01 = aLo * bHi; + long b10 = aHi * bLo; + long b11 = aHi * bHi; + + long b00Lo = Integer.toUnsignedLong((int)b00); + long b00Hi = Integer.toUnsignedLong((int)(b00 >> 32)); + + long mid1 = b10 + b00Hi; + long mid1Lo = Integer.toUnsignedLong((int)(mid1)); + long mid1Hi = Integer.toUnsignedLong((int)(mid1 >> 32)); + + long mid2 = b01 + mid1Lo; + long mid2Lo = Integer.toUnsignedLong((int)(mid2)); + long mid2Hi = Integer.toUnsignedLong((int)(mid2 >> 32)); + + long pHi = b11 + mid1Hi + mid2Hi; + long pLo = (mid2Lo << 32) | b00Lo; + + productHi[0] = pHi; + return pLo; + } + + static long shiftright128(long lo, long hi, int dist) { + return (hi << (64 - dist)) | (lo >>> dist); + } + + static long mulShift64(long m, long[] mul, int j) { + long[] high1Buf = new long[1]; + long low1 = umul128(m, mul[1], high1Buf); + long high1 = high1Buf[0]; + long[] high0Buf = high1Buf; // reuse alloc since we extracted high1 into its own long + umul128(m, mul[0], high0Buf); + long high0 = high0Buf[0]; + long sum = high0 + low1; + if (Long.compareUnsigned(sum, high0) < 0) { + ++high1; + } + return shiftright128(sum, high1, j - 64); + } + + // Precomputed powers of ten from 10^0 to 10^22. These + // can be represented exactly using the double type. + private static final double[] POWER_OF_TEN = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, + 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}; + + // Main conversion function: decimal mantissa+exponent to IEEE 754 double + // Optimized for JSON parsing with fast paths for edge cases + public static double ryuS2dFromParts(long m10, int m10digits, int e10, boolean signedM) { + // From Daniel Lemire's fast_double_parser which is an extraction of part of 'How to Read + // Floating Point Numbers Accurately' by Willian D. Clinger: https://www.cs.uoregon.edu/Reports/TR-1990-001.pdf + if (-22 <= e10 && e10 <= 22 && Long.compareUnsigned(m10, 9007199254740991L) <= 0) { + double value = (double) m10; // Lossless conversion for 0 <= i <= 2^53 - 1. + + if (e10 < 0) { + value /= POWER_OF_TEN[-e10]; + } else { + value *= POWER_OF_TEN[e10]; + } + + return signedM ? -value : value; + } + + // Fast path: handle zero explicitly (e.g., "0.0", "0e0") or overflow/underflow early + if (m10 == 0 || m10digits + e10 <= -324) return signedM ? -0.0 : 0.0; + + // Overflow to infinity + if (m10digits + e10 >= 310) return Double.POSITIVE_INFINITY; + + // Convert decimal to binary: m10 * 10^e10 = m2 * 2^e2 + int e2; + long m2; + boolean trailingZeros; + + if (e10 >= 0) { + // Positive exponent: multiply by 5^e10 and adjust binary exponent + e2 = floorLog2(m10) + e10 + log2pow5(e10) - (DOUBLE_MANTISSA_BITS + 1); + int j = e2 - e10 - ceilLog2pow5(e10) + DOUBLE_POW5_BITCOUNT; + m2 = mulShift64(m10, DOUBLE_POW5_SPLIT[e10], j); + trailingZeros = e2 < e10 || (e2 - e10 < 64 && multipleOfPowerOf2(m10)); + } else { + // Negative exponent: divide by 5^(-e10) + e2 = floorLog2(m10) + e10 - ceilLog2pow5(-e10) - (DOUBLE_MANTISSA_BITS + 1); + int j = e2 - e10 + ceilLog2pow5(-e10) - 1 + DOUBLE_POW5_INV_BITCOUNT; + m2 = mulShift64(m10, DOUBLE_POW5_INV_SPLIT[-e10], j); + trailingZeros = multipleOfPowerOf5(m10, -e10); + } + + // Compute IEEE 754 exponent + int ieee_e2 = Math.max(0, e2 + DOUBLE_EXPONENT_BIAS + floorLog2(m2)); + int signedML = signedM ? 1 : 0; + + if (ieee_e2 > 0x7fe) { + // Overflow to infinity + return int64Bits2Double((signedML << 63) | 0x7ff0000000000000L); + } + + // Compute shift amount for rounding + int shift = (ieee_e2 == 0 ? 1 : ieee_e2) - e2 - DOUBLE_EXPONENT_BIAS - DOUBLE_MANTISSA_BITS; + + // IEEE 754 round-to-even (banker's rounding) + trailingZeros &= (m2 & ((1L << (shift - 1)) - 1)) == 0; + long lastRemovedBit = (m2 >> (shift - 1)) & 1; + int roundUp = (lastRemovedBit != 0) && (!trailingZeros || (((m2 >> shift) & 1) != 0)) ? 1 : 0; + + long ieee_m2 = (m2 >> shift) + roundUp; + ieee_m2 &= (1L << DOUBLE_MANTISSA_BITS) - 1; + + if (ieee_m2 == 0 && roundUp == 1) { + ieee_e2++; + } + + // Pack sign, exponent, and mantissa into IEEE 754 format + // Match original Ryu: group sign+exponent, then shift and add mantissa + long ieee = (((signedML << DOUBLE_EXPONENT_BITS) | (long)ieee_e2) << DOUBLE_MANTISSA_BITS) | ieee_m2; + return int64Bits2Double(ieee); + } +} diff --git a/java/src/json/ext/StringDecoder.java b/java/src/json/ext/StringDecoder.java index 091b09f5..28874acd 100644 --- a/java/src/json/ext/StringDecoder.java +++ b/java/src/json/ext/StringDecoder.java @@ -186,8 +186,7 @@ private int readHex(ThreadContext context) { } else if (digit >= 'A' && digit <= 'F') { digitValue = 10 + digit - 'A'; } else { - throw new NumberFormatException("Invalid base 16 number " - + src.subSequence(numberStart, numberStart + length)); + throw invalidUnicodeEscape(context, numberStart); } result = result * 16 + digitValue; } @@ -220,4 +219,14 @@ protected RaiseException invalidEscape(ThreadContext context) { return Utils.newException(context, Utils.M_PARSER_ERROR, context.runtime.newString(message)); } + + protected RaiseException invalidUnicodeEscape(ThreadContext context, int escapeStart) { + ByteList message = new ByteList( + ByteList.plain("incomplete unicode character escape sequence at ")); + // Point at the backslash-u that introduced the escape. + int start = Math.max(escapeStart - 2, 0); + message.append(src, start, srcEnd - start); + return Utils.newException(context, Utils.M_PARSER_ERROR, + context.runtime.newString(message)); + } } diff --git a/java/src/json/ext/StringScanner.java b/java/src/json/ext/StringScanner.java new file mode 100644 index 00000000..5ca59d17 --- /dev/null +++ b/java/src/json/ext/StringScanner.java @@ -0,0 +1,140 @@ +package json.ext; + +import java.nio.ByteBuffer; + +/** + * Scans the body of a JSON string for its closing quote, reporting whether the + * body can be copied verbatim (the "plain" fast path) or must be handed to + * {@link StringDecoder} for escape expansion and UTF-8/control validation. + * + *

The default implementation is SWAR (8 bytes per step). A vectorized + * subclass ({@code VectorizedStringScanner}) is loaded reflectively when the + * {@code jruby.json.useVectorizedParser} system property is set and the + * {@code jdk.incubator.vector} module is available; otherwise this SWAR + * implementation is used. Instances are stateless and therefore shared. + */ +class StringScanner { + /** Set in the returned bits when the whole body is plain printable ASCII. */ + static final long PLAIN_BIT = 1L << 32; + /** Returned when no closing quote is found before {@code end}. */ + static final long NOT_FOUND = -1L; + + private static final long HIGH_BITS = 0x8080808080808080L; + private static final long ONES = 0x0101010101010101L; + + private static final long SPACES = 0x2020202020202020L; + private static final long DOUBLE_QUOTES = 0x2222222222222222L; + private static final long BACKSLASHES = 0x5C5C5C5C5C5C5C5CL; + + private static final String VECTORIZED_SCANNER_CLASS = "json.ext.VectorizedStringScanner"; + private static final String USE_VECTORIZED_PARSER_PROP = "jruby.json.useVectorizedParser"; + private static final String USE_VECTORIZED_PARSER_DEFAULT = "false"; + + private static final StringScanner INSTANCE; + + static { + StringScanner scanner = new StringScanner(); + String enable = System.getProperty(USE_VECTORIZED_PARSER_PROP, USE_VECTORIZED_PARSER_DEFAULT); + if ("true".equalsIgnoreCase(enable) || "1".equals(enable)) { + try { + Class vectorized = StringScanner.class.getClassLoader() + .loadClass(VECTORIZED_SCANNER_CLASS); + scanner = (StringScanner) vectorized.getDeclaredConstructor().newInstance(); + } catch (Throwable t) { + // jdk.incubator.vector unavailable (or any load failure): + // keep the SWAR implementation. + scanner = new StringScanner(); + } + } + INSTANCE = scanner; + } + + static StringScanner getInstance() { + return INSTANCE; + } + + /** + * Scans {@code data[start..end)} for the closing quote, honouring backslash + * escapes. + * + * @param chunks a little-endian {@link ByteBuffer} over {@code data}, used + * for the 8-byte SWAR reads (the vectorized subclass reads + * {@code data} directly and ignores it). + * @return packed result: the low 32 bits hold the index of the closing + * quote, or {@code -1} ({@link #NOT_FOUND}) when none is found + * before {@code end}; {@link #PLAIN_BIT} is set when the entire body + * is plain printable ASCII (no escape, no ASCII control character, + * and no non-ASCII byte) and can be copied verbatim. + */ + long scan(byte[] data, ByteBuffer chunks, int start, int end) { + int p = start; + boolean plain = true; + + outer: + while (true) { + // SWAR: skip 8-byte chunks that contain nothing interesting. + while (p + 8 <= end) { + long x = chunks.getLong(p); + // Due to the byte-by-byte handling if we match an interesting byte, + // if we already know this is a non-ASCII-only string, we simply + // look for quotes and backslashes. + long m = plain ? stringScanMask(x) : quoteBackslashMask(x); + if (m == 0) { + p += 8; + } else { + p += Long.numberOfTrailingZeros(m) >>> 3; + break; + } + } + // If we match on a byte above and/or tail handling for <8 remaining bytes. + while (p < end) { + int b = data[p] & 0xFF; + if (b == '"') { + return ((long) p) | (plain ? PLAIN_BIT : 0L); + } + if (b == '\\') { + plain = false; + p += 2; // skip the backslash and the escaped byte + continue outer; + } + if (b < 0x20 || b >= 0x80) { + plain = false; + p++; + continue outer; + } + p++; + } + return NOT_FOUND; + } + } + + /** + * Returns a mask whose high bit (0x80) is set in every lane of {@code x} + * that needs scalar attention: an ASCII control character (< 0x20), a + * double quote, a backslash, or a non-ASCII byte (high bit set). Returns 0 + * when the whole 8-byte chunk is printable ASCII copyable verbatim. + */ + private static long stringScanMask(long x) { + long control = (x - SPACES) & ~x; // bytes < 0x20 (ASCII) + long high = x; // bit 0x80 set iff non-ASCII + long q = x ^ DOUBLE_QUOTES; + long quote = (q - ONES) & ~q; + long s = x ^ BACKSLASHES; + long bslash = (s - ONES) & ~s; + return (control | high | quote | bslash) & HIGH_BITS; + } + + /** + * Like {@link #stringScanMask} but only flags double quotes and backslashes. + * Used once a string is known to require the decoder, so the remaining scan + * for the closing quote still skips clean chunks (including multi-byte + * UTF-8) eight bytes at a time. + */ + private static long quoteBackslashMask(long x) { + long q = x ^ DOUBLE_QUOTES; + long quote = (q - ONES) & ~q; + long s = x ^ BACKSLASHES; + long bslash = (s - ONES) & ~s; + return (quote | bslash) & HIGH_BITS; + } +} diff --git a/java/src/json/ext/VectorizedStringScanner.java b/java/src/json/ext/VectorizedStringScanner.java new file mode 100644 index 00000000..8a9c1e3e --- /dev/null +++ b/java/src/json/ext/VectorizedStringScanner.java @@ -0,0 +1,72 @@ +package json.ext; + +import java.nio.ByteBuffer; + +import jdk.incubator.vector.ByteVector; +import jdk.incubator.vector.VectorMask; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +final class VectorizedStringScanner extends StringScanner { + private static final VectorSpecies SP = ByteVector.SPECIES_PREFERRED; + private static final ByteVector ZERO = ByteVector.zero(SP); + private static final ByteVector TWO = ByteVector.broadcast(SP, 2); + private static final ByteVector THIRTY_THREE = ByteVector.broadcast(SP, 33); + private static final ByteVector BACKSLASH = ByteVector.broadcast(SP, '\\'); + private static final ByteVector DQUOTE = ByteVector.broadcast(SP, '"'); + + @Override + long scan(byte[] data, ByteBuffer chunks, int start, int end) { + final int width = SP.length(); + int p = start; + boolean plain = true; + + // The same structure as the StringEncoder. The logic is + // duplicated for maximum inlining. + outer: + while (true) { + while (p + width <= end) { + ByteVector chunk = ByteVector.fromArray(SP, data, p); + VectorMask interesting = + plain ? interestingLanes(chunk) : quoteOrBackslashLanes(chunk); + if (interesting.anyTrue()) { + p += interesting.firstTrue(); + break; + } + p += width; + } + while (p < end) { + int b = data[p] & 0xFF; + if (b == '"') { + return ((long) p) | (plain ? PLAIN_BIT : 0L); + } + if (b == '\\') { + plain = false; + p += 2; // skip the backslash and the escaped byte + continue outer; + } + if (b < 0x20 || b >= 0x80) { + plain = false; + p++; + continue outer; + } + p++; + } + return NOT_FOUND; + } + } + + // Lanes that are control characters, double quotes, backslashes or non-ASCII. + private static VectorMask interestingLanes(ByteVector chunk) { + VectorMask negative = chunk.lt(ZERO); + VectorMask lowOrQuote = chunk.lanewise(VectorOperators.XOR, TWO) + .lt(THIRTY_THREE) + .andNot(negative); + return lowOrQuote.or(chunk.eq(BACKSLASH)).or(negative); + } + + // Lanes that are a double quote or a backslash (non-plain phase). + private static VectorMask quoteOrBackslashLanes(ByteVector chunk) { + return chunk.eq(DQUOTE).or(chunk.eq(BACKSLASH)); + } +}