Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
   package divconq.json3.base;
   
   import java.io.*;
   
  import static divconq.json3.JsonTokenId.*;

This is a concrete implementation of JsonParser, which is based on a java.io.Reader to handle low-level character conversion tasks.
  
  public class ReaderBasedJsonParser extends ParserBase
  {
  	
      // Latin1 encoding is not supported, but we do use 8-bit subset for
      // pre-processing task, to simplify first pass, keep it fast.
      protected final static int[] _icLatin1 = CharTypes.getInputCodeLatin1();
  
      /*
      /**********************************************************
      /* Input configuration
      /**********************************************************
       */

    
Reader that can be used for reading more content, if one buffer from input source, but in some cases pre-loaded buffer is handed to the parser.
  
      protected Reader _reader;

    
Current buffer from which data is read; generally data is read into buffer from input source.
  
      protected char[] _inputBuffer;
  
      /*
      /**********************************************************
      /* Parsing state
      /**********************************************************
       */
    
    
Flag that indicates that the current token has not yet been fully processed, and needs to be finished for some access (or skipped to obtain the next token)
  
      protected boolean _tokenIncomplete = false;
  
      /*
      /**********************************************************
      /* Life-cycle
      /**********************************************************
       */
  
      /*
       * Method called when input comes as a {@link java.io.Reader}, and buffer allocation
       * can be done using default mechanism.
       */
      public ReaderBasedJsonParser(IOContext ctxtReader r)
      {
          super(ctxt);
           = r;
           = ctxt.allocTokenBuffer();
           = 0;
           = 0;
      }
      
      @Override
      public int releaseBuffered(Writer wthrows IOException {
          int count =  - ;
          if (count < 1) { return 0; }
          // let's just advance ptr to end
          int origPtr = ;
          w.write(origPtrcount);
          return count;
      }
  
      @Override public Object getInputSource() { return ; }
  
      @Override
      protected boolean loadMore() throws IOException
      {
           += ;
           -= ;
  
          if ( != null) {
              int count = .read(, 0, .);
              if (count > 0) {
                   = 0;
                   = count;
                  return true;
             }
             // End of input
             _closeInput();
             // Should never return 0, so let's fail
             if (count == 0) {
                 throw new IOException("Reader returned 0 characters when trying to read "+);
             }
         }
         return false;
     }
 
     protected char getNextChar(String eofMsgthrows IOException {
         if ( >= ) {
             if (!loadMore()) { _reportInvalidEOF(eofMsg); }
         }
         return [++];
     }
 
     @Override
     protected void _closeInput() throws IOException {
         /* 25-Nov-2008, tatus: As per [JACKSON-16] we are not to call close()
          *   on the underlying Reader, unless we "own" it, or auto-closing
          *   feature is enabled.
          *   One downside is that when using our optimized
          *   Reader (granted, we only do that for UTF-32...) this
          *   means that buffer recycling won't work correctly.
          */
         if ( != null) {
             if (.isResourceManaged()) {
                 .close();
             }
              = null;
         }
     }
     
     /*
     /**********************************************************
     /* Public API, data access
     /**********************************************************
      */
     
     /*
      * Method for accessing textual representation of the current event;
      * if no current event (before first call to {@link #nextToken}, or
      * after encountering end-of-input), returns null.
      * Method can be called for any event.
      */
     @Override
     public final String getText() throws IOException
     {
         JsonToken t = ;
         if (t == .) {
             if () {
                  = false;
                 _finishString(); // only strings can be incomplete
             }
             return .contentsAsString();
         }
         return _getText2(t);
     }
 
     // // // Let's override default impls for improved performance
     
     // @since 2.1
     @Override
     public final String getValueAsString() throws IOException
     {
         if ( == .) {
             if () {
                  = false;
                 _finishString(); // only strings can be incomplete
             }
             return .contentsAsString();
         }
         return super.getValueAsString(null);
     }
     
     // @since 2.1
     @Override
     public final String getValueAsString(String defValuethrows IOException {
         if ( == .) {
             if () {
                  = false;
                 _finishString(); // only strings can be incomplete
             }
             return .contentsAsString();
         }
         return super.getValueAsString(defValue);
     }
 
     protected final String _getText2(JsonToken t) {
         if (t == null) {
             return null;
         }
         switch (t.id()) {
         case :
             return .getCurrentName();
 
         case :
             // fall through
         case :
         case :
             return .contentsAsString();
         default:
             return t.asString();
         }
     }
 
     @Override
     public final char[] getTextCharacters() throws IOException
     {
         if ( != null) { // null only before/after document
             switch (.id()) {
             case :
                 if (!) {
                     String name = .getCurrentName();
                     int nameLen = name.length();
                     if ( == null) {
                          = .allocNameCopyBuffer(nameLen);
                     } else if (. < nameLen) {
                          = new char[nameLen];
                     }
                     name.getChars(0, nameLen, 0);
                      = true;
                 }
                 return ;
     
             case :
                 if () {
                      = false;
                     _finishString(); // only strings can be incomplete
                 }
                 // fall through
             case :
             case :
                 return .getTextBuffer();
                 
             default:
                 return .asCharArray();
             }
         }
         return null;
     }
 
     @Override
     public final int getTextLength() throws IOException
     {
         if ( != null) { // null only before/after document
             switch (.id()) {
                 
             case :
                 return .getCurrentName().length();
             case :
                 if () {
                      = false;
                     _finishString(); // only strings can be incomplete
                 }
                 // fall through
             case :
             case :
                 return .size();
                 
             default:
                 return .asCharArray().length;
             }
         }
         return 0;
     }
 
     @Override
     public final int getTextOffset() throws IOException
     {
         // Most have offset of 0, only some may have other values:
         if ( != null) {
             switch (.id()) {
             case :
                 return 0;
             case :
                 if () {
                      = false;
                     _finishString(); // only strings can be incomplete
                 }
                 // fall through
             case :
             case :
                 return .getTextOffset();
             default:
             }
         }
         return 0;
     }
 
     @Override
     public byte[] getBinaryValue(Base64Variant b64variantthrows IOException
     {
         if ( != . &&
                 ( != . ||  == null)) {
             _reportError("Current token ("++") not VALUE_STRING or VALUE_EMBEDDED_OBJECT, can not access as binary");
         }
         /* To ensure that we won't see inconsistent data, better clear up
          * state...
          */
         if () {
             try {
                  = _decodeBase64(b64variant);
             } catch (IllegalArgumentException iae) {
                 throw _constructError("Failed to decode VALUE_STRING as base64 ("+b64variant+"): "+iae.getMessage());
             }
             /* let's clear incomplete only now; allows for accessing other
              * textual content in error cases
              */
              = false;
         } else { // may actually require conversion...
             if ( == null) {
                 ByteArrayBuilder builder = _getByteArrayBuilder();
                 _decodeBase64(getText(), builderb64variant);
                  = builder.toByteArray();
             }
         }
         return ;
     }
     
     @Override
     public int readBinaryValue(Base64Variant b64variantOutputStream outthrows IOException
     {
         // if we have already read the token, just use whatever we may have
         if (! ||  != .) {
             byte[] b = getBinaryValue(b64variant);
             out.write(b);
             return b.length;
         }
         // otherwise do "real" incremental parsing...
         byte[] buf = .allocBase64Buffer();
         try {
             return _readBinary(b64variantoutbuf);
         } finally {
             .releaseBase64Buffer(buf);
         }
     }
 
     protected int _readBinary(Base64Variant b64variantOutputStream outbyte[] bufferthrows IOException
     {
         int outputPtr = 0;
         final int outputEnd = buffer.length - 3;
         int outputCount = 0;
 
         while (true) {
             // first, we'll skip preceding white space, if any
             char ch;
             do {
                 if ( >= ) {
                     loadMoreGuaranteed();
                 }
                 ch = [++];
             } while (ch <= );
             int bits = b64variant.decodeBase64Char(ch);
             if (bits < 0) { // reached the end, fair and square?
                 if (ch == '"') {
                     break;
                 }
                 bits = _decodeBase64Escape(b64variantch, 0);
                 if (bits < 0) { // white space to skip
                     continue;
                 }
             }
 
             // enough room? If not, flush
             if (outputPtr > outputEnd) {
                 outputCount += outputPtr;
                 out.write(buffer, 0, outputPtr);
                 outputPtr = 0;
             }
 
             int decodedData = bits;
 
             // then second base64 char; can't get padding yet, nor ws
 
             if ( >= ) {
                 loadMoreGuaranteed();
             }
             ch = [++];
             bits = b64variant.decodeBase64Char(ch);
             if (bits < 0) {
                 bits = _decodeBase64Escape(b64variantch, 1);
             }
             decodedData = (decodedData << 6) | bits;
 
             // third base64 char; can be padding, but not ws
             if ( >= ) {
                 loadMoreGuaranteed();
             }
             ch = [++];
             bits = b64variant.decodeBase64Char(ch);
 
             // First branch: can get padding (-> 1 byte)
             if (bits < 0) {
                 if (bits != .) {
                     // as per [JACKSON-631], could also just be 'missing'  padding
                     if (ch == '"' && !b64variant.usesPadding()) {
                         decodedData >>= 4;
                         buffer[outputPtr++] = (bytedecodedData;
                         break;
                     }
                     bits = _decodeBase64Escape(b64variantch, 2);
                 }
                 if (bits == .) {
                     // Ok, must get padding
                     if ( >= ) {
                         loadMoreGuaranteed();
                     }
                     ch = [++];
                     if (!b64variant.usesPaddingChar(ch)) {
                         throw reportInvalidBase64Char(b64variantch, 3, "expected padding character '"+b64variant.getPaddingChar()+"'");
                     }
                     // Got 12 bits, only need 8, need to shift
                     decodedData >>= 4;
                     buffer[outputPtr++] = (bytedecodedData;
                     continue;
                 }
             }
             // Nope, 2 or 3 bytes
             decodedData = (decodedData << 6) | bits;
             // fourth and last base64 char; can be padding, but not ws
             if ( >= ) {
                 loadMoreGuaranteed();
             }
             ch = [++];
             bits = b64variant.decodeBase64Char(ch);
             if (bits < 0) {
                 if (bits != .) {
                     // as per [JACKSON-631], could also just be 'missing'  padding
                     if (ch == '"' && !b64variant.usesPadding()) {
                         decodedData >>= 2;
                         buffer[outputPtr++] = (byte) (decodedData >> 8);
                         buffer[outputPtr++] = (bytedecodedData;
                         break;
                     }
                     bits = _decodeBase64Escape(b64variantch, 3);
                 }
                 if (bits == .) {
                     /* With padding we only get 2 bytes; but we have
                      * to shift it a bit so it is identical to triplet
                      * case with partial output.
                      * 3 chars gives 3x6 == 18 bits, of which 2 are
                      * dummies, need to discard:
                      */
                     decodedData >>= 2;
                     buffer[outputPtr++] = (byte) (decodedData >> 8);
                     buffer[outputPtr++] = (bytedecodedData;
                     continue;
                 }
             }
             // otherwise, our triplet is now complete
             decodedData = (decodedData << 6) | bits;
             buffer[outputPtr++] = (byte) (decodedData >> 16);
             buffer[outputPtr++] = (byte) (decodedData >> 8);
             buffer[outputPtr++] = (bytedecodedData;
         }
          = false;
         if (outputPtr > 0) {
             outputCount += outputPtr;
             out.write(buffer, 0, outputPtr);
         }
         return outputCount;
     }
 
     /*
    /**********************************************************
    /* Public API, traversal
    /**********************************************************
     */
 
     /*
      * @return Next token from the stream, if any found, or null
      *   to indicate end-of-input
      */
     @Override
     public final JsonToken nextToken() throws IOException
     {
          = ;
 
         /* First: field names are special -- we will always tokenize
          * (part of) value along with field name to simplify
          * state handling. If so, can and need to use secondary token:
          */
         if ( == .) {
             return _nextAfterName();
         }
         if () {
             _skipString(); // only strings can be partial
         }
         int i = _skipWSOrEnd();
         if (i < 0) { // end-of-input
             /* 19-Feb-2009, tatu: Should actually close/release things
              *    like input source, symbol table and recyclable buffers now.
              */
             close();
             return ( = null);
         }
 
         /* First, need to ensure we know the starting location of token
          * after skipping leading white space
          */
          =  +  - 1;
          = ;
          =  -  - 1;
 
         // finally: clear any data retained so far
          = null;
 
         // Closing scope?
         if (i == ) {
             if (!.inArray()) {
                 _reportMismatchedEndMarker(i'}');
             }
              = .getParent();
             return ( = .);
         }
         if (i == ) {
             if (!.inObject()) {
                 _reportMismatchedEndMarker(i']');
             }
              = .getParent();
             return ( = .);
         }
 
         // Nope: do we then expect a comma?
         if (.expectComma()) {
             i = _skipComma(i);
         }
 
         /* And should we now have a name? Always true for
          * Object contexts, since the intermediate 'expect-value'
          * state is never retained.
          */
         boolean inObject = .inObject();
         if (inObject) {
            // First, field name itself:
             String name = (i == ) ? _parseName() : _handleOddName(i);
             .setCurrentName(name);
              = .;
             i = _skipColon();
         }
 
         // Ok: we must have a value... what is it?
 
         JsonToken t;
 
         switch (i) {
         case '"':
              = true;
             t = .;
             break;
         case '[':
             if (!inObject) {
             }
             t = .;
             break;
         case '{':
             if (!inObject) {
             }
             t = .;
             break;
         case ']':
         case '}':
             // Error: neither is valid at this point; valid closers have
             // been handled earlier
             _reportUnexpectedChar(i"expected a value");
         case 't':
             _matchTrue();
             t = .;
             break;
         case 'f':
             _matchFalse();
             t = .;
             break;
         case 'n':
             _matchNull();
             t = .;
             break;
 
         case '-':
             /* Should we have separate handling for plus? Although
              * it is not allowed per se, it may be erroneously used,
              * and could be indicate by a more specific error message.
              */
             t = _parseNegNumber();
             break;
         case '0':
         case '1':
         case '2':
         case '3':
         case '4':
         case '5':
         case '6':
         case '7':
         case '8':
         case '9':
             t = _parsePosNumber(i);
             break;
         default:
             t = _handleOddValue(i);
             break;
         }
 
         if (inObject) {
              = t;
             return ;
         }
          = t;
         return t;
     }
 
     private final JsonToken _nextAfterName()
     {
          = false// need to invalidate if it was copied
         JsonToken t = ;
          = null;
         // Also: may need to start new context?
         if (t == .) {
         } else if (t == .) {
         }
         return ( = t);
     }
 
     /*
     @Override
     public boolean nextFieldName(SerializableString str)
          throws IOException
      */
 
     // note: identical to one in UTF8StreamJsonParser
     @Override
     public final String nextTextValue() throws IOException
     {
         if ( == .) { // mostly copied from '_nextAfterName'
              = false;
             JsonToken t = ;
              = null;
              = t;
             if (t == .) {
                 if () {
                      = false;
                     _finishString();
                 }
                 return .contentsAsString();
             }
             if (t == .) {
             } else if (t == .) {
             }
             return null;
         }
         // !!! TODO: optimize this case as well
         return (nextToken() == .) ? getText() : null;
     }
 
     // note: identical to one in Utf8StreamParser
     @Override
     public final int nextIntValue(int defaultValuethrows IOException
     {
         if ( == .) {
              = false;
             JsonToken t = ;
              = null;
              = t;
             if (t == .) {
                 return getIntValue();
             }
             if (t == .) {
             } else if (t == .) {
             }
             return defaultValue;
         }
         // !!! TODO: optimize this case as well
         return (nextToken() == .) ? getIntValue() : defaultValue;
     }
 
     // note: identical to one in Utf8StreamParser
     @Override
     public final long nextLongValue(long defaultValuethrows IOException
     {
         if ( == .) { // mostly copied from '_nextAfterName'
              = false;
             JsonToken t = ;
              = null;
              = t;
             if (t == .) {
                 return getLongValue();
             }
             if (t == .) {
             } else if (t == .) {
             }
             return defaultValue;
         }
         // !!! TODO: optimize this case as well
         return (nextToken() == .) ? getLongValue() : defaultValue;
     }
 
     // note: identical to one in UTF8StreamJsonParser
     @Override
     public final Boolean nextBooleanValue() throws IOException
     {
         if ( == .) { // mostly copied from '_nextAfterName'
              = false;
             JsonToken t = ;
              = null;
              = t;
             if (t == .) {
                 return .;
             }
             if (t == .) {
                 return .;
             }
             if (t == .) {
             } else if (t == .) {
             }
             return null;
         }
         JsonToken t = nextToken();
         if (t != null) {
             int id = t.id();
             if (id == return .;
             if (id == return .;
         }
         return null;
     }
 
     /*
     /**********************************************************
     /* Internal methods, number parsing
     /**********************************************************
      */
 
     /*
      * Initial parsing method for number values. It needs to be able
      * to parse enough input to be able to determine whether the
      * value is to be considered a simple integer value, or a more
      * generic decimal value: latter of which needs to be expressed
      * as a floating point number. The basic rule is that if the number
      * has no fractional or exponential part, it is an integer; otherwise
      * a floating point number.
      *<p>
      * Because much of input has to be processed in any case, no partial
      * parsing is done: all input text will be stored for further
      * processing. However, actual numeric value conversion will be
      * deferred, since it is usually the most complicated and costliest
      * part of processing.
      */
     protected final JsonToken _parsePosNumber(int chthrows IOException
     {
         /* Although we will always be complete with respect to textual
          * representation (that is, all characters will be parsed),
          * actual conversion to a number is deferred. Thus, need to
          * note that no representations are valid yet
          */
         int ptr = ;
         int startPtr = ptr-1; // to include digit already read
         final int inputLen = ;
 
         // One special case, leading zero(es):
         if (ch == ) {
             return _parseNumber2(falsestartPtr);
         }
             
         /* First, let's see if the whole number is contained within
          * the input buffer unsplit. This should be the common case;
          * and to simplify processing, we will just reparse contents
          * in the alternative case (number split on buffer boundary)
          */
         
         int intLen = 1; // already got one
         
         // First let's get the obligatory integer part:
         int_loop:
         while (true) {
             if (ptr >= inputLen) {
                  = startPtr;
                 return _parseNumber2(falsestartPtr);
             }
             ch = (int[ptr++];
             if (ch <  || ch > ) {
                 break int_loop;
             }
             ++intLen;
         }
         if (ch ==  || ch ==  || ch == ) {
              = ptr;
             return _parseFloat(chstartPtrptrfalseintLen);
         }
         // Got it all: let's add to text buffer for parsing, access
         --ptr// need to push back following separator
          = ptr;
         // As per #105, need separating space between root values; check here
         if (.inRoot()) {
             _verifyRootSpace(ch);
         }
         int len = ptr-startPtr;
         .resetWithShared(startPtrlen);
         return resetInt(falseintLen);
     }
 
     private final JsonToken _parseFloat(int chint startPtrint ptrboolean negint intLen)
         throws IOException
     {
         final int inputLen = ;
         int fractLen = 0;
 
         // And then see if we get other parts
         if (ch == '.') { // yes, fraction
             fract_loop:
             while (true) {
                 if (ptr >= inputLen) {
                     return _parseNumber2(negstartPtr);
                 }
                 ch = (int[ptr++];
                 if (ch <  || ch > ) {
                     break fract_loop;
                 }
                 ++fractLen;
             }
             // must be followed by sequence of ints, one minimum
             if (fractLen == 0) {
                 reportUnexpectedNumberChar(ch"Decimal point not followed by a digit");
             }
         }
         int expLen = 0;
         if (ch == 'e' || ch == 'E') { // and/or exponent
             if (ptr >= inputLen) {
                  = startPtr;
                 return _parseNumber2(negstartPtr);
             }
             // Sign indicator?
             ch = (int[ptr++];
             if (ch ==  || ch == ) { // yup, skip for now
                 if (ptr >= inputLen) {
                      = startPtr;
                     return _parseNumber2(negstartPtr);
                 }
                 ch = (int[ptr++];
             }
             while (ch <=  && ch >= ) {
                 ++expLen;
                 if (ptr >= inputLen) {
                      = startPtr;
                     return _parseNumber2(negstartPtr);
                 }
                 ch = (int[ptr++];
             }
             // must be followed by sequence of ints, one minimum
             if (expLen == 0) {
                 reportUnexpectedNumberChar(ch"Exponent indicator not followed by a digit");
             }
         }
         --ptr// need to push back following separator
          = ptr;
         // As per #105, need separating space between root values; check here
         if (.inRoot()) {
             _verifyRootSpace(ch);
         }
         int len = ptr-startPtr;
         .resetWithShared(startPtrlen);
         // And there we have it!
         return resetFloat(negintLenfractLenexpLen);
     }
 
     protected final JsonToken _parseNegNumber() throws IOException
     {
         int ptr = ;
         int startPtr = ptr-1; // to include sign/digit already read
         final int inputLen = ;
 
         if (ptr >= inputLen) {
             return _parseNumber2(truestartPtr);
         }
         int ch = [ptr++];
         // First check: must have a digit to follow minus sign
         if (ch >  || ch < ) {
              = ptr;
             return _handleInvalidNumberStart(chtrue);
         }
         // One special case, leading zero(es):
         if (ch == ) {
             return _parseNumber2(truestartPtr);
         }
         int intLen = 1; // already got one
         
         // First let's get the obligatory integer part:
         int_loop:
         while (true) {
             if (ptr >= inputLen) {
                 return _parseNumber2(truestartPtr);
             }
             ch = (int[ptr++];
             if (ch <  || ch > ) {
                 break int_loop;
             }
             ++intLen;
         }
 
         if (ch ==  || ch ==  || ch == ) {
              = ptr;
             return _parseFloat(chstartPtrptrtrueintLen);
         }
         --ptr;
          = ptr;
         if (.inRoot()) {
             _verifyRootSpace(ch);
         }
         int len = ptr-startPtr;
         .resetWithShared(startPtrlen);
         return resetInt(trueintLen);
     }
 
     /*
      * Method called to parse a number, when the primary parse
      * method has failed to parse it, due to it being split on
      * buffer boundary. As a result code is very similar, except
      * that it has to explicitly copy contents to the text buffer
      * instead of just sharing the main input buffer.
      */
     private final JsonToken _parseNumber2(boolean negint startPtrthrows IOException
     {
          = neg ? (startPtr+1) : startPtr;
         char[] outBuf = .emptyAndGetCurrentSegment();
         int outPtr = 0;
 
         // Need to prepend sign?
         if (neg) {
             outBuf[outPtr++] = '-';
         }
 
         // This is the place to do leading-zero check(s) too:
         int intLen = 0;
         char c = ( < ) ? [++] : getNextChar("No digit following minus sign");
         if (c == '0') {
             c = _verifyNoLeadingZeroes();
         }
         boolean eof = false;
 
         // Ok, first the obligatory integer part:
         int_loop:
         while (c >= '0' && c <= '9') {
             ++intLen;
             if (outPtr >= outBuf.length) {
                 outBuf = .finishCurrentSegment();
                 outPtr = 0;
             }
             outBuf[outPtr++] = c;
             if ( >=  && !loadMore()) {
                 // EOF is legal for main level int values
                 c = ;
                 eof = true;
                 break int_loop;
             }
             c = [++];
         }
         // Also, integer part is not optional
         if (intLen == 0) {
             reportInvalidNumber("Missing integer part (next char "+_getCharDesc(c)+")");
         }
 
         int fractLen = 0;
         // And then see if we get other parts
         if (c == '.') { // yes, fraction
             outBuf[outPtr++] = c;
 
             fract_loop:
             while (true) {
                 if ( >=  && !loadMore()) {
                     eof = true;
                     break fract_loop;
                 }
                 c = [++];
                 if (c <  || c > ) {
                     break fract_loop;
                 }
                 ++fractLen;
                 if (outPtr >= outBuf.length) {
                     outBuf = .finishCurrentSegment();
                     outPtr = 0;
                 }
                 outBuf[outPtr++] = c;
             }
             // must be followed by sequence of ints, one minimum
             if (fractLen == 0) {
                 reportUnexpectedNumberChar(c"Decimal point not followed by a digit");
             }
         }
        int expLen = 0;
        if (c == 'e' || c == 'E') { // exponent?
            if (outPtr >= outBuf.length) {
                outBuf = .finishCurrentSegment();
                outPtr = 0;
            }
            outBuf[outPtr++] = c;
            // Not optional, can require that we get one more char
            c = ( < ) ? [++]
                : getNextChar("expected a digit for number exponent");
            // Sign indicator?
            if (c == '-' || c == '+') {
                if (outPtr >= outBuf.length) {
                    outBuf = .finishCurrentSegment();
                    outPtr = 0;
                }
                outBuf[outPtr++] = c;
                // Likewise, non optional:
                c = ( < ) ? [++]
                    : getNextChar("expected a digit for number exponent");
            }
            exp_loop:
            while (c <=  && c >= ) {
                ++expLen;
                if (outPtr >= outBuf.length) {
                    outBuf = .finishCurrentSegment();
                    outPtr = 0;
                }
                outBuf[outPtr++] = c;
                if ( >=  && !loadMore()) {
                    eof = true;
                    break exp_loop;
                }
                c = [++];
            }
            // must be followed by sequence of ints, one minimum
            if (expLen == 0) {
                reportUnexpectedNumberChar(c"Exponent indicator not followed by a digit");
            }
        }
        // Ok; unless we hit end-of-input, need to push last char read back
        if (!eof) {
            --;
            if (.inRoot()) {
                _verifyRootSpace(c);
            }
        }
        .setCurrentLength(outPtr);
        // And there we have it!
        return reset(negintLenfractLenexpLen);
    }
    /*
     * Method called when we have seen one zero, and want to ensure
     * it is not followed by another
     */
    private final char _verifyNoLeadingZeroes() throws IOException
    {
        // Fast case first:
        if ( < ) {
            char ch = [];
            // if not followed by a number (probably '.'); return zero as is, to be included
            if (ch < '0' || ch > '9') {
                return '0';
            }
        }
        // and offline the less common case
        return _verifyNLZ2();
    }
        
    private char _verifyNLZ2() throws IOException
    {
        if ( >=  && !loadMore()) {
            return '0';
        }
        char ch = [];
        if (ch < '0' || ch > '9') {
            return '0';
        }
        
        // ALLOW_NUMERIC_LEADING_ZEROS 
        // if so, just need to skip either all zeroes (if followed by number); or all but one (if non-number)
        ++// Leading zero to be skipped
        if (ch == ) {
            while ( <  || loadMore()) {
                ch = [];
                if (ch < '0' || ch > '9') { // followed by non-number; retain one zero
                    return '0';
                }
                ++// skip previous zero
                if (ch != '0') { // followed by other number; return 
                    break;
                }
            }
        }