mas01mj@732: /* mas01mj@732: Copyright (c) 2008, Adobe Systems Incorporated mas01mj@732: All rights reserved. mas01mj@732: mas01mj@732: Redistribution and use in source and binary forms, with or without mas01mj@732: modification, are permitted provided that the following conditions are mas01mj@732: met: mas01mj@732: mas01mj@732: * Redistributions of source code must retain the above copyright notice, mas01mj@732: this list of conditions and the following disclaimer. mas01mj@732: mas01mj@732: * Redistributions in binary form must reproduce the above copyright mas01mj@732: notice, this list of conditions and the following disclaimer in the mas01mj@732: documentation and/or other materials provided with the distribution. mas01mj@732: mas01mj@732: * Neither the name of Adobe Systems Incorporated nor the names of its mas01mj@732: contributors may be used to endorse or promote products derived from mas01mj@732: this software without specific prior written permission. mas01mj@732: mas01mj@732: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS mas01mj@732: IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, mas01mj@732: THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR mas01mj@732: PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR mas01mj@732: CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, mas01mj@732: EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, mas01mj@732: PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR mas01mj@732: PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF mas01mj@732: LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING mas01mj@732: NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS mas01mj@732: SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. mas01mj@732: */ mas01mj@732: mas01mj@732: package com.adobe.serialization.json { mas01mj@732: mas01mj@732: public class JSONTokenizer { mas01mj@732: mas01mj@732: /** mas01mj@732: * Flag indicating if the tokenizer should only recognize mas01mj@732: * standard JSON tokens. Setting to false allows mas01mj@732: * tokens such as NaN and allows numbers to be formatted as mas01mj@732: * hex, etc. mas01mj@732: */ mas01mj@732: private var strict:Boolean; mas01mj@732: mas01mj@732: /** The object that will get parsed from the JSON string */ mas01mj@732: private var obj:Object; mas01mj@732: mas01mj@732: /** The JSON string to be parsed */ mas01mj@732: private var jsonString:String; mas01mj@732: mas01mj@732: /** The current parsing location in the JSON string */ mas01mj@732: private var loc:int; mas01mj@732: mas01mj@732: /** The current character in the JSON string during parsing */ mas01mj@732: private var ch:String; mas01mj@732: mas01mj@732: /** mas01mj@732: * The regular expression used to make sure the string does not mas01mj@732: * contain invalid control characters. mas01mj@732: */ mas01mj@732: private var controlCharsRegExp:RegExp = /[\x00-\x1F]/; mas01mj@732: mas01mj@732: /** mas01mj@732: * Constructs a new JSONDecoder to parse a JSON string mas01mj@732: * into a native object. mas01mj@732: * mas01mj@732: * @param s The JSON string to be converted mas01mj@732: * into a native object mas01mj@732: */ mas01mj@732: public function JSONTokenizer( s:String, strict:Boolean ) mas01mj@732: { mas01mj@732: jsonString = s; mas01mj@732: this.strict = strict; mas01mj@732: loc = 0; mas01mj@732: mas01mj@732: // prime the pump by getting the first character mas01mj@732: nextChar(); mas01mj@732: } mas01mj@732: mas01mj@732: /** mas01mj@732: * Gets the next token in the input sting and advances mas01mj@732: * the character to the next character after the token mas01mj@732: */ mas01mj@732: public function getNextToken():JSONToken mas01mj@732: { mas01mj@732: var token:JSONToken = new JSONToken(); mas01mj@732: mas01mj@732: // skip any whitespace / comments since the last mas01mj@732: // token was read mas01mj@732: skipIgnored(); mas01mj@732: mas01mj@732: // examine the new character and see what we have... mas01mj@732: switch ( ch ) mas01mj@732: { mas01mj@732: case '{': mas01mj@732: token.type = JSONTokenType.LEFT_BRACE; mas01mj@732: token.value = '{'; mas01mj@732: nextChar(); mas01mj@732: break mas01mj@732: mas01mj@732: case '}': mas01mj@732: token.type = JSONTokenType.RIGHT_BRACE; mas01mj@732: token.value = '}'; mas01mj@732: nextChar(); mas01mj@732: break mas01mj@732: mas01mj@732: case '[': mas01mj@732: token.type = JSONTokenType.LEFT_BRACKET; mas01mj@732: token.value = '['; mas01mj@732: nextChar(); mas01mj@732: break mas01mj@732: mas01mj@732: case ']': mas01mj@732: token.type = JSONTokenType.RIGHT_BRACKET; mas01mj@732: token.value = ']'; mas01mj@732: nextChar(); mas01mj@732: break mas01mj@732: mas01mj@732: case ',': mas01mj@732: token.type = JSONTokenType.COMMA; mas01mj@732: token.value = ','; mas01mj@732: nextChar(); mas01mj@732: break mas01mj@732: mas01mj@732: case ':': mas01mj@732: token.type = JSONTokenType.COLON; mas01mj@732: token.value = ':'; mas01mj@732: nextChar(); mas01mj@732: break; mas01mj@732: mas01mj@732: case 't': // attempt to read true mas01mj@732: var possibleTrue:String = "t" + nextChar() + nextChar() + nextChar(); mas01mj@732: mas01mj@732: if ( possibleTrue == "true" ) mas01mj@732: { mas01mj@732: token.type = JSONTokenType.TRUE; mas01mj@732: token.value = true; mas01mj@732: nextChar(); mas01mj@732: } mas01mj@732: else mas01mj@732: { mas01mj@732: parseError( "Expecting 'true' but found " + possibleTrue ); mas01mj@732: } mas01mj@732: mas01mj@732: break; mas01mj@732: mas01mj@732: case 'f': // attempt to read false mas01mj@732: var possibleFalse:String = "f" + nextChar() + nextChar() + nextChar() + nextChar(); mas01mj@732: mas01mj@732: if ( possibleFalse == "false" ) mas01mj@732: { mas01mj@732: token.type = JSONTokenType.FALSE; mas01mj@732: token.value = false; mas01mj@732: nextChar(); mas01mj@732: } mas01mj@732: else mas01mj@732: { mas01mj@732: parseError( "Expecting 'false' but found " + possibleFalse ); mas01mj@732: } mas01mj@732: mas01mj@732: break; mas01mj@732: mas01mj@732: case 'n': // attempt to read null mas01mj@732: var possibleNull:String = "n" + nextChar() + nextChar() + nextChar(); mas01mj@732: mas01mj@732: if ( possibleNull == "null" ) mas01mj@732: { mas01mj@732: token.type = JSONTokenType.NULL; mas01mj@732: token.value = null; mas01mj@732: nextChar(); mas01mj@732: } mas01mj@732: else mas01mj@732: { mas01mj@732: parseError( "Expecting 'null' but found " + possibleNull ); mas01mj@732: } mas01mj@732: mas01mj@732: break; mas01mj@732: mas01mj@732: case 'N': // attempt to read NaN mas01mj@732: var possibleNaN:String = "N" + nextChar() + nextChar(); mas01mj@732: mas01mj@732: if ( possibleNaN == "NaN" ) mas01mj@732: { mas01mj@732: token.type = JSONTokenType.NAN; mas01mj@732: token.value = NaN; mas01mj@732: nextChar(); mas01mj@732: } mas01mj@732: else mas01mj@732: { mas01mj@732: parseError( "Expecting 'NaN' but found " + possibleNaN ); mas01mj@732: } mas01mj@732: mas01mj@732: break; mas01mj@732: mas01mj@732: case '"': // the start of a string mas01mj@732: token = readString(); mas01mj@732: break; mas01mj@732: mas01mj@732: default: mas01mj@732: // see if we can read a number mas01mj@732: if ( isDigit( ch ) || ch == '-' ) mas01mj@732: { mas01mj@732: token = readNumber(); mas01mj@732: } mas01mj@732: else if ( ch == '' ) mas01mj@732: { mas01mj@732: // check for reading past the end of the string mas01mj@732: return null; mas01mj@732: } mas01mj@732: else mas01mj@732: { mas01mj@732: // not sure what was in the input string - it's not mas01mj@732: // anything we expected mas01mj@732: parseError( "Unexpected " + ch + " encountered" ); mas01mj@732: } mas01mj@732: } mas01mj@732: mas01mj@732: return token; mas01mj@732: } mas01mj@732: mas01mj@732: /** mas01mj@732: * Attempts to read a string from the input string. Places mas01mj@732: * the character location at the first character after the mas01mj@732: * string. It is assumed that ch is " before this method is called. mas01mj@732: * mas01mj@732: * @return the JSONToken with the string value if a string could mas01mj@732: * be read. Throws an error otherwise. mas01mj@732: */ mas01mj@732: private function readString():JSONToken mas01mj@732: { mas01mj@732: // Rather than examine the string character-by-character, it's mas01mj@732: // faster to use indexOf to try to and find the closing quote character mas01mj@732: // and then replace escape sequences after the fact. mas01mj@732: mas01mj@732: // Start at the current input stream position mas01mj@732: var quoteIndex:int = loc; mas01mj@732: do mas01mj@732: { mas01mj@732: // Find the next quote in the input stream mas01mj@732: quoteIndex = jsonString.indexOf( "\"", quoteIndex ); mas01mj@732: mas01mj@732: if ( quoteIndex >= 0 ) mas01mj@732: { mas01mj@732: // We found the next double quote character in the string, but we need mas01mj@732: // to make sure it is not part of an escape sequence. mas01mj@732: mas01mj@732: // Keep looping backwards while the previous character is a backslash mas01mj@732: var backspaceCount:int = 0; mas01mj@732: var backspaceIndex:int = quoteIndex - 1; mas01mj@732: while ( jsonString.charAt( backspaceIndex ) == "\\" ) mas01mj@732: { mas01mj@732: backspaceCount++; mas01mj@732: backspaceIndex--; mas01mj@732: } mas01mj@732: mas01mj@732: // If we have an even number of backslashes, that means this is the ending quote mas01mj@732: if ( backspaceCount % 2 == 0 ) mas01mj@732: { mas01mj@732: break; mas01mj@732: } mas01mj@732: mas01mj@732: // At this point, the quote was determined to be part of an escape sequence mas01mj@732: // so we need to move past the quote index to look for the next one mas01mj@732: quoteIndex++; mas01mj@732: } mas01mj@732: else // There are no more quotes in the string and we haven't found the end yet mas01mj@732: { mas01mj@732: parseError( "Unterminated string literal" ); mas01mj@732: } mas01mj@732: } while ( true ); mas01mj@732: mas01mj@732: // Unescape the string mas01mj@732: // the token for the string we'll try to read mas01mj@732: var token:JSONToken = new JSONToken(); mas01mj@732: token.type = JSONTokenType.STRING; mas01mj@732: // Attach resulting string to the token to return it mas01mj@732: token.value = unescapeString( jsonString.substr( loc, quoteIndex - loc ) ); mas01mj@732: mas01mj@732: // Move past the closing quote in the input string. This updates the next mas01mj@732: // character in the input stream to be the character one after the closing quote mas01mj@732: loc = quoteIndex + 1; mas01mj@732: nextChar(); mas01mj@732: mas01mj@732: return token; mas01mj@732: } mas01mj@732: mas01mj@732: /** mas01mj@732: * Convert all JavaScript escape characters into normal characters mas01mj@732: * mas01mj@732: * @param input The input string to convert mas01mj@732: * @return Original string with escape characters replaced by real characters mas01mj@732: */ mas01mj@732: public function unescapeString( input:String ):String mas01mj@732: { mas01mj@732: // Issue #104 - If the string contains any unescaped control characters, this mas01mj@732: // is an error in strict mode mas01mj@732: if ( strict && controlCharsRegExp.test( input ) ) mas01mj@732: { mas01mj@732: parseError( "String contains unescaped control character (0x00-0x1F)" ); mas01mj@732: } mas01mj@732: mas01mj@732: var result:String = ""; mas01mj@732: var backslashIndex:int = 0; mas01mj@732: var nextSubstringStartPosition:int = 0; mas01mj@732: var len:int = input.length; mas01mj@732: do mas01mj@732: { mas01mj@732: // Find the next backslash in the input mas01mj@732: backslashIndex = input.indexOf( '\\', nextSubstringStartPosition ); mas01mj@732: mas01mj@732: if ( backslashIndex >= 0 ) mas01mj@732: { mas01mj@732: result += input.substr( nextSubstringStartPosition, backslashIndex - nextSubstringStartPosition ); mas01mj@732: mas01mj@732: // Move past the backslash and next character (all escape sequences are mas01mj@732: // two characters, except for \u, which will advance this further) mas01mj@732: nextSubstringStartPosition = backslashIndex + 2; mas01mj@732: mas01mj@732: // Check the next character so we know what to escape mas01mj@732: var afterBackslashIndex:int = backslashIndex + 1; mas01mj@732: var escapedChar:String = input.charAt( afterBackslashIndex ); mas01mj@732: switch ( escapedChar ) mas01mj@732: { mas01mj@732: // Try to list the most common expected cases first to improve performance mas01mj@732: mas01mj@732: case '"': result += '"'; break; // quotation mark mas01mj@732: case '\\': result += '\\'; break; // reverse solidus mas01mj@732: case 'n': result += '\n'; break; // newline mas01mj@732: case 'r': result += '\r'; break; // carriage return mas01mj@732: case 't': result += '\t'; break; // horizontal tab mas01mj@732: mas01mj@732: // Convert a unicode escape sequence to it's character value mas01mj@732: case 'u': mas01mj@732: mas01mj@732: // Save the characters as a string we'll convert to an int mas01mj@732: var hexValue:String = ""; mas01mj@732: mas01mj@732: // Make sure there are enough characters in the string leftover mas01mj@732: if ( nextSubstringStartPosition + 4 > len ) mas01mj@732: { mas01mj@732: parseError( "Unexpected end of input. Expecting 4 hex digits after \\u." ); mas01mj@732: } mas01mj@732: mas01mj@732: // Try to find 4 hex characters mas01mj@732: for ( var i:int = nextSubstringStartPosition; i < nextSubstringStartPosition + 4; i++ ) mas01mj@732: { mas01mj@732: // get the next character and determine mas01mj@732: // if it's a valid hex digit or not mas01mj@732: var possibleHexChar:String = input.charAt( i ); mas01mj@732: if ( !isHexDigit( possibleHexChar ) ) mas01mj@732: { mas01mj@732: parseError( "Excepted a hex digit, but found: " + possibleHexChar ); mas01mj@732: } mas01mj@732: mas01mj@732: // Valid hex digit, add it to the value mas01mj@732: hexValue += possibleHexChar; mas01mj@732: } mas01mj@732: mas01mj@732: // Convert hexValue to an integer, and use that mas01mj@732: // integer value to create a character to add mas01mj@732: // to our string. mas01mj@732: result += String.fromCharCode( parseInt( hexValue, 16 ) ); mas01mj@732: // Move past the 4 hex digits that we just read mas01mj@732: nextSubstringStartPosition += 4; mas01mj@732: break; mas01mj@732: mas01mj@732: case 'f': result += '\f'; break; // form feed mas01mj@732: case '/': result += '/'; break; // solidus mas01mj@732: case 'b': result += '\b'; break; // bell mas01mj@732: default: result += '\\' + escapedChar; // Couldn't unescape the sequence, so just pass it through mas01mj@732: } mas01mj@732: } mas01mj@732: else mas01mj@732: { mas01mj@732: // No more backslashes to replace, append the rest of the string mas01mj@732: result += input.substr( nextSubstringStartPosition ); mas01mj@732: break; mas01mj@732: } mas01mj@732: mas01mj@732: } while ( nextSubstringStartPosition < len ); mas01mj@732: mas01mj@732: return result; mas01mj@732: } mas01mj@732: mas01mj@732: /** mas01mj@732: * Attempts to read a number from the input string. Places mas01mj@732: * the character location at the first character after the mas01mj@732: * number. mas01mj@732: * mas01mj@732: * @return The JSONToken with the number value if a number could mas01mj@732: * be read. Throws an error otherwise. mas01mj@732: */ mas01mj@732: private function readNumber():JSONToken mas01mj@732: { mas01mj@732: // the string to accumulate the number characters mas01mj@732: // into that we'll convert to a number at the end mas01mj@732: var input:String = ""; mas01mj@732: mas01mj@732: // check for a negative number mas01mj@732: if ( ch == '-' ) mas01mj@732: { mas01mj@732: input += '-'; mas01mj@732: nextChar(); mas01mj@732: } mas01mj@732: mas01mj@732: // the number must start with a digit mas01mj@732: if ( !isDigit( ch ) ) mas01mj@732: { mas01mj@732: parseError( "Expecting a digit" ); mas01mj@732: } mas01mj@732: mas01mj@732: // 0 can only be the first digit if it mas01mj@732: // is followed by a decimal point mas01mj@732: if ( ch == '0' ) mas01mj@732: { mas01mj@732: input += ch; mas01mj@732: nextChar(); mas01mj@732: mas01mj@732: // make sure no other digits come after 0 mas01mj@732: if ( isDigit( ch ) ) mas01mj@732: { mas01mj@732: parseError( "A digit cannot immediately follow 0" ); mas01mj@732: } mas01mj@732: // unless we have 0x which starts a hex number, but this mas01mj@732: // doesn't match JSON spec so check for not strict mode. mas01mj@732: else if ( !strict && ch == 'x' ) mas01mj@732: { mas01mj@732: // include the x in the input mas01mj@732: input += ch; mas01mj@732: nextChar(); mas01mj@732: mas01mj@732: // need at least one hex digit after 0x to mas01mj@732: // be valid mas01mj@732: if ( isHexDigit( ch ) ) mas01mj@732: { mas01mj@732: input += ch; mas01mj@732: nextChar(); mas01mj@732: } mas01mj@732: else mas01mj@732: { mas01mj@732: parseError( "Number in hex format require at least one hex digit after \"0x\"" ); mas01mj@732: } mas01mj@732: mas01mj@732: // consume all of the hex values mas01mj@732: while ( isHexDigit( ch ) ) mas01mj@732: { mas01mj@732: input += ch; mas01mj@732: nextChar(); mas01mj@732: } mas01mj@732: } mas01mj@732: } mas01mj@732: else mas01mj@732: { mas01mj@732: // read numbers while we can mas01mj@732: while ( isDigit( ch ) ) mas01mj@732: { mas01mj@732: input += ch; mas01mj@732: nextChar(); mas01mj@732: } mas01mj@732: } mas01mj@732: mas01mj@732: // check for a decimal value mas01mj@732: if ( ch == '.' ) mas01mj@732: { mas01mj@732: input += '.'; mas01mj@732: nextChar(); mas01mj@732: mas01mj@732: // after the decimal there has to be a digit mas01mj@732: if ( !isDigit( ch ) ) mas01mj@732: { mas01mj@732: parseError( "Expecting a digit" ); mas01mj@732: } mas01mj@732: mas01mj@732: // read more numbers to get the decimal value mas01mj@732: while ( isDigit( ch ) ) mas01mj@732: { mas01mj@732: input += ch; mas01mj@732: nextChar(); mas01mj@732: } mas01mj@732: } mas01mj@732: mas01mj@732: // check for scientific notation mas01mj@732: if ( ch == 'e' || ch == 'E' ) mas01mj@732: { mas01mj@732: input += "e" mas01mj@732: nextChar(); mas01mj@732: // check for sign mas01mj@732: if ( ch == '+' || ch == '-' ) mas01mj@732: { mas01mj@732: input += ch; mas01mj@732: nextChar(); mas01mj@732: } mas01mj@732: mas01mj@732: // require at least one number for the exponent mas01mj@732: // in this case mas01mj@732: if ( !isDigit( ch ) ) mas01mj@732: { mas01mj@732: parseError( "Scientific notation number needs exponent value" ); mas01mj@732: } mas01mj@732: mas01mj@732: // read in the exponent mas01mj@732: while ( isDigit( ch ) ) mas01mj@732: { mas01mj@732: input += ch; mas01mj@732: nextChar(); mas01mj@732: } mas01mj@732: } mas01mj@732: mas01mj@732: // convert the string to a number value mas01mj@732: var num:Number = Number( input ); mas01mj@732: mas01mj@732: if ( isFinite( num ) && !isNaN( num ) ) mas01mj@732: { mas01mj@732: // the token for the number that we've read mas01mj@732: var token:JSONToken = new JSONToken(); mas01mj@732: token.type = JSONTokenType.NUMBER; mas01mj@732: token.value = num; mas01mj@732: return token; mas01mj@732: } mas01mj@732: else mas01mj@732: { mas01mj@732: parseError( "Number " + num + " is not valid!" ); mas01mj@732: } mas01mj@732: mas01mj@732: return null; mas01mj@732: } mas01mj@732: mas01mj@732: /** mas01mj@732: * Reads the next character in the input mas01mj@732: * string and advances the character location. mas01mj@732: * mas01mj@732: * @return The next character in the input string, or mas01mj@732: * null if we've read past the end. mas01mj@732: */ mas01mj@732: private function nextChar():String mas01mj@732: { mas01mj@732: return ch = jsonString.charAt( loc++ ); mas01mj@732: } mas01mj@732: mas01mj@732: /** mas01mj@732: * Advances the character location past any mas01mj@732: * sort of white space and comments mas01mj@732: */ mas01mj@732: private function skipIgnored():void mas01mj@732: { mas01mj@732: var originalLoc:int; mas01mj@732: mas01mj@732: // keep trying to skip whitespace and comments as long mas01mj@732: // as we keep advancing past the original location mas01mj@732: do mas01mj@732: { mas01mj@732: originalLoc = loc; mas01mj@732: skipWhite(); mas01mj@732: skipComments(); mas01mj@732: } mas01mj@732: while ( originalLoc != loc ); mas01mj@732: } mas01mj@732: mas01mj@732: /** mas01mj@732: * Skips comments in the input string, either mas01mj@732: * single-line or multi-line. Advances the character mas01mj@732: * to the first position after the end of the comment. mas01mj@732: */ mas01mj@732: private function skipComments():void mas01mj@732: { mas01mj@732: if ( ch == '/' ) mas01mj@732: { mas01mj@732: // Advance past the first / to find out what type of comment mas01mj@732: nextChar(); mas01mj@732: switch ( ch ) mas01mj@732: { mas01mj@732: case '/': // single-line comment, read through end of line mas01mj@732: mas01mj@732: // Loop over the characters until we find mas01mj@732: // a newline or until there's no more characters left mas01mj@732: do mas01mj@732: { mas01mj@732: nextChar(); mas01mj@732: } mas01mj@732: while ( ch != '\n' && ch != '' ) mas01mj@732: mas01mj@732: // move past the \n mas01mj@732: nextChar(); mas01mj@732: mas01mj@732: break; mas01mj@732: mas01mj@732: case '*': // multi-line comment, read until closing */ mas01mj@732: mas01mj@732: // move past the opening * mas01mj@732: nextChar(); mas01mj@732: mas01mj@732: // try to find a trailing */ mas01mj@732: while ( true ) mas01mj@732: { mas01mj@732: if ( ch == '*' ) mas01mj@732: { mas01mj@732: // check to see if we have a closing / mas01mj@732: nextChar(); mas01mj@732: if ( ch == '/') mas01mj@732: { mas01mj@732: // move past the end of the closing */ mas01mj@732: nextChar(); mas01mj@732: break; mas01mj@732: } mas01mj@732: } mas01mj@732: else mas01mj@732: { mas01mj@732: // move along, looking if the next character is a * mas01mj@732: nextChar(); mas01mj@732: } mas01mj@732: mas01mj@732: // when we're here we've read past the end of mas01mj@732: // the string without finding a closing */, so error mas01mj@732: if ( ch == '' ) mas01mj@732: { mas01mj@732: parseError( "Multi-line comment not closed" ); mas01mj@732: } mas01mj@732: } mas01mj@732: mas01mj@732: break; mas01mj@732: mas01mj@732: // Can't match a comment after a /, so it's a parsing error mas01mj@732: default: mas01mj@732: parseError( "Unexpected " + ch + " encountered (expecting '/' or '*' )" ); mas01mj@732: } mas01mj@732: } mas01mj@732: mas01mj@732: } mas01mj@732: mas01mj@732: mas01mj@732: /** mas01mj@732: * Skip any whitespace in the input string and advances mas01mj@732: * the character to the first character after any possible mas01mj@732: * whitespace. mas01mj@732: */ mas01mj@732: private function skipWhite():void mas01mj@732: { mas01mj@732: // As long as there are spaces in the input mas01mj@732: // stream, advance the current location pointer mas01mj@732: // past them mas01mj@732: while ( isWhiteSpace( ch ) ) mas01mj@732: { mas01mj@732: nextChar(); mas01mj@732: } mas01mj@732: mas01mj@732: } mas01mj@732: mas01mj@732: /** mas01mj@732: * Determines if a character is whitespace or not. mas01mj@732: * mas01mj@732: * @return True if the character passed in is a whitespace mas01mj@732: * character mas01mj@732: */ mas01mj@732: private function isWhiteSpace( ch:String ):Boolean mas01mj@732: { mas01mj@732: // Check for the whitespace defined in the spec mas01mj@732: if ( ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' ) mas01mj@732: { mas01mj@732: return true; mas01mj@732: } mas01mj@732: // If we're not in strict mode, we also accept non-breaking space mas01mj@732: else if ( !strict && ch.charCodeAt( 0 ) == 160 ) mas01mj@732: { mas01mj@732: return true; mas01mj@732: } mas01mj@732: mas01mj@732: return false; mas01mj@732: } mas01mj@732: mas01mj@732: /** mas01mj@732: * Determines if a character is a digit [0-9]. mas01mj@732: * mas01mj@732: * @return True if the character passed in is a digit mas01mj@732: */ mas01mj@732: private function isDigit( ch:String ):Boolean mas01mj@732: { mas01mj@732: return ( ch >= '0' && ch <= '9' ); mas01mj@732: } mas01mj@732: mas01mj@732: /** mas01mj@732: * Determines if a character is a hex digit [0-9A-Fa-f]. mas01mj@732: * mas01mj@732: * @return True if the character passed in is a hex digit mas01mj@732: */ mas01mj@732: private function isHexDigit( ch:String ):Boolean mas01mj@732: { mas01mj@732: return ( isDigit( ch ) || ( ch >= 'A' && ch <= 'F' ) || ( ch >= 'a' && ch <= 'f' ) ); mas01mj@732: } mas01mj@732: mas01mj@732: /** mas01mj@732: * Raises a parsing error with a specified message, tacking mas01mj@732: * on the error location and the original string. mas01mj@732: * mas01mj@732: * @param message The message indicating why the error occurred mas01mj@732: */ mas01mj@732: public function parseError( message:String ):void mas01mj@732: { mas01mj@732: throw new JSONParseError( message, loc, jsonString ); mas01mj@732: } mas01mj@732: } mas01mj@732: mas01mj@732: }