annotate bindings/as3/ext/com/adobe/serialization/json/JSONTokenizer.as @ 770:c54bc2ffbf92 tip

update tags
author convert-repo
date Fri, 16 Dec 2011 11:34:01 +0000
parents 3a0b9700b3d2
children
rev   line source
mas01mj@732 1 /*
mas01mj@732 2 Copyright (c) 2008, Adobe Systems Incorporated
mas01mj@732 3 All rights reserved.
mas01mj@732 4
mas01mj@732 5 Redistribution and use in source and binary forms, with or without
mas01mj@732 6 modification, are permitted provided that the following conditions are
mas01mj@732 7 met:
mas01mj@732 8
mas01mj@732 9 * Redistributions of source code must retain the above copyright notice,
mas01mj@732 10 this list of conditions and the following disclaimer.
mas01mj@732 11
mas01mj@732 12 * Redistributions in binary form must reproduce the above copyright
mas01mj@732 13 notice, this list of conditions and the following disclaimer in the
mas01mj@732 14 documentation and/or other materials provided with the distribution.
mas01mj@732 15
mas01mj@732 16 * Neither the name of Adobe Systems Incorporated nor the names of its
mas01mj@732 17 contributors may be used to endorse or promote products derived from
mas01mj@732 18 this software without specific prior written permission.
mas01mj@732 19
mas01mj@732 20 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
mas01mj@732 21 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
mas01mj@732 22 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
mas01mj@732 23 PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
mas01mj@732 24 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
mas01mj@732 25 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
mas01mj@732 26 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
mas01mj@732 27 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
mas01mj@732 28 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
mas01mj@732 29 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
mas01mj@732 30 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
mas01mj@732 31 */
mas01mj@732 32
mas01mj@732 33 package com.adobe.serialization.json {
mas01mj@732 34
mas01mj@732 35 public class JSONTokenizer {
mas01mj@732 36
mas01mj@732 37 /**
mas01mj@732 38 * Flag indicating if the tokenizer should only recognize
mas01mj@732 39 * standard JSON tokens. Setting to <code>false</code> allows
mas01mj@732 40 * tokens such as NaN and allows numbers to be formatted as
mas01mj@732 41 * hex, etc.
mas01mj@732 42 */
mas01mj@732 43 private var strict:Boolean;
mas01mj@732 44
mas01mj@732 45 /** The object that will get parsed from the JSON string */
mas01mj@732 46 private var obj:Object;
mas01mj@732 47
mas01mj@732 48 /** The JSON string to be parsed */
mas01mj@732 49 private var jsonString:String;
mas01mj@732 50
mas01mj@732 51 /** The current parsing location in the JSON string */
mas01mj@732 52 private var loc:int;
mas01mj@732 53
mas01mj@732 54 /** The current character in the JSON string during parsing */
mas01mj@732 55 private var ch:String;
mas01mj@732 56
mas01mj@732 57 /**
mas01mj@732 58 * The regular expression used to make sure the string does not
mas01mj@732 59 * contain invalid control characters.
mas01mj@732 60 */
mas01mj@732 61 private var controlCharsRegExp:RegExp = /[\x00-\x1F]/;
mas01mj@732 62
mas01mj@732 63 /**
mas01mj@732 64 * Constructs a new JSONDecoder to parse a JSON string
mas01mj@732 65 * into a native object.
mas01mj@732 66 *
mas01mj@732 67 * @param s The JSON string to be converted
mas01mj@732 68 * into a native object
mas01mj@732 69 */
mas01mj@732 70 public function JSONTokenizer( s:String, strict:Boolean )
mas01mj@732 71 {
mas01mj@732 72 jsonString = s;
mas01mj@732 73 this.strict = strict;
mas01mj@732 74 loc = 0;
mas01mj@732 75
mas01mj@732 76 // prime the pump by getting the first character
mas01mj@732 77 nextChar();
mas01mj@732 78 }
mas01mj@732 79
mas01mj@732 80 /**
mas01mj@732 81 * Gets the next token in the input sting and advances
mas01mj@732 82 * the character to the next character after the token
mas01mj@732 83 */
mas01mj@732 84 public function getNextToken():JSONToken
mas01mj@732 85 {
mas01mj@732 86 var token:JSONToken = new JSONToken();
mas01mj@732 87
mas01mj@732 88 // skip any whitespace / comments since the last
mas01mj@732 89 // token was read
mas01mj@732 90 skipIgnored();
mas01mj@732 91
mas01mj@732 92 // examine the new character and see what we have...
mas01mj@732 93 switch ( ch )
mas01mj@732 94 {
mas01mj@732 95 case '{':
mas01mj@732 96 token.type = JSONTokenType.LEFT_BRACE;
mas01mj@732 97 token.value = '{';
mas01mj@732 98 nextChar();
mas01mj@732 99 break
mas01mj@732 100
mas01mj@732 101 case '}':
mas01mj@732 102 token.type = JSONTokenType.RIGHT_BRACE;
mas01mj@732 103 token.value = '}';
mas01mj@732 104 nextChar();
mas01mj@732 105 break
mas01mj@732 106
mas01mj@732 107 case '[':
mas01mj@732 108 token.type = JSONTokenType.LEFT_BRACKET;
mas01mj@732 109 token.value = '[';
mas01mj@732 110 nextChar();
mas01mj@732 111 break
mas01mj@732 112
mas01mj@732 113 case ']':
mas01mj@732 114 token.type = JSONTokenType.RIGHT_BRACKET;
mas01mj@732 115 token.value = ']';
mas01mj@732 116 nextChar();
mas01mj@732 117 break
mas01mj@732 118
mas01mj@732 119 case ',':
mas01mj@732 120 token.type = JSONTokenType.COMMA;
mas01mj@732 121 token.value = ',';
mas01mj@732 122 nextChar();
mas01mj@732 123 break
mas01mj@732 124
mas01mj@732 125 case ':':
mas01mj@732 126 token.type = JSONTokenType.COLON;
mas01mj@732 127 token.value = ':';
mas01mj@732 128 nextChar();
mas01mj@732 129 break;
mas01mj@732 130
mas01mj@732 131 case 't': // attempt to read true
mas01mj@732 132 var possibleTrue:String = "t" + nextChar() + nextChar() + nextChar();
mas01mj@732 133
mas01mj@732 134 if ( possibleTrue == "true" )
mas01mj@732 135 {
mas01mj@732 136 token.type = JSONTokenType.TRUE;
mas01mj@732 137 token.value = true;
mas01mj@732 138 nextChar();
mas01mj@732 139 }
mas01mj@732 140 else
mas01mj@732 141 {
mas01mj@732 142 parseError( "Expecting 'true' but found " + possibleTrue );
mas01mj@732 143 }
mas01mj@732 144
mas01mj@732 145 break;
mas01mj@732 146
mas01mj@732 147 case 'f': // attempt to read false
mas01mj@732 148 var possibleFalse:String = "f" + nextChar() + nextChar() + nextChar() + nextChar();
mas01mj@732 149
mas01mj@732 150 if ( possibleFalse == "false" )
mas01mj@732 151 {
mas01mj@732 152 token.type = JSONTokenType.FALSE;
mas01mj@732 153 token.value = false;
mas01mj@732 154 nextChar();
mas01mj@732 155 }
mas01mj@732 156 else
mas01mj@732 157 {
mas01mj@732 158 parseError( "Expecting 'false' but found " + possibleFalse );
mas01mj@732 159 }
mas01mj@732 160
mas01mj@732 161 break;
mas01mj@732 162
mas01mj@732 163 case 'n': // attempt to read null
mas01mj@732 164 var possibleNull:String = "n" + nextChar() + nextChar() + nextChar();
mas01mj@732 165
mas01mj@732 166 if ( possibleNull == "null" )
mas01mj@732 167 {
mas01mj@732 168 token.type = JSONTokenType.NULL;
mas01mj@732 169 token.value = null;
mas01mj@732 170 nextChar();
mas01mj@732 171 }
mas01mj@732 172 else
mas01mj@732 173 {
mas01mj@732 174 parseError( "Expecting 'null' but found " + possibleNull );
mas01mj@732 175 }
mas01mj@732 176
mas01mj@732 177 break;
mas01mj@732 178
mas01mj@732 179 case 'N': // attempt to read NaN
mas01mj@732 180 var possibleNaN:String = "N" + nextChar() + nextChar();
mas01mj@732 181
mas01mj@732 182 if ( possibleNaN == "NaN" )
mas01mj@732 183 {
mas01mj@732 184 token.type = JSONTokenType.NAN;
mas01mj@732 185 token.value = NaN;
mas01mj@732 186 nextChar();
mas01mj@732 187 }
mas01mj@732 188 else
mas01mj@732 189 {
mas01mj@732 190 parseError( "Expecting 'NaN' but found " + possibleNaN );
mas01mj@732 191 }
mas01mj@732 192
mas01mj@732 193 break;
mas01mj@732 194
mas01mj@732 195 case '"': // the start of a string
mas01mj@732 196 token = readString();
mas01mj@732 197 break;
mas01mj@732 198
mas01mj@732 199 default:
mas01mj@732 200 // see if we can read a number
mas01mj@732 201 if ( isDigit( ch ) || ch == '-' )
mas01mj@732 202 {
mas01mj@732 203 token = readNumber();
mas01mj@732 204 }
mas01mj@732 205 else if ( ch == '' )
mas01mj@732 206 {
mas01mj@732 207 // check for reading past the end of the string
mas01mj@732 208 return null;
mas01mj@732 209 }
mas01mj@732 210 else
mas01mj@732 211 {
mas01mj@732 212 // not sure what was in the input string - it's not
mas01mj@732 213 // anything we expected
mas01mj@732 214 parseError( "Unexpected " + ch + " encountered" );
mas01mj@732 215 }
mas01mj@732 216 }
mas01mj@732 217
mas01mj@732 218 return token;
mas01mj@732 219 }
mas01mj@732 220
mas01mj@732 221 /**
mas01mj@732 222 * Attempts to read a string from the input string. Places
mas01mj@732 223 * the character location at the first character after the
mas01mj@732 224 * string. It is assumed that ch is " before this method is called.
mas01mj@732 225 *
mas01mj@732 226 * @return the JSONToken with the string value if a string could
mas01mj@732 227 * be read. Throws an error otherwise.
mas01mj@732 228 */
mas01mj@732 229 private function readString():JSONToken
mas01mj@732 230 {
mas01mj@732 231 // Rather than examine the string character-by-character, it's
mas01mj@732 232 // faster to use indexOf to try to and find the closing quote character
mas01mj@732 233 // and then replace escape sequences after the fact.
mas01mj@732 234
mas01mj@732 235 // Start at the current input stream position
mas01mj@732 236 var quoteIndex:int = loc;
mas01mj@732 237 do
mas01mj@732 238 {
mas01mj@732 239 // Find the next quote in the input stream
mas01mj@732 240 quoteIndex = jsonString.indexOf( "\"", quoteIndex );
mas01mj@732 241
mas01mj@732 242 if ( quoteIndex >= 0 )
mas01mj@732 243 {
mas01mj@732 244 // We found the next double quote character in the string, but we need
mas01mj@732 245 // to make sure it is not part of an escape sequence.
mas01mj@732 246
mas01mj@732 247 // Keep looping backwards while the previous character is a backslash
mas01mj@732 248 var backspaceCount:int = 0;
mas01mj@732 249 var backspaceIndex:int = quoteIndex - 1;
mas01mj@732 250 while ( jsonString.charAt( backspaceIndex ) == "\\" )
mas01mj@732 251 {
mas01mj@732 252 backspaceCount++;
mas01mj@732 253 backspaceIndex--;
mas01mj@732 254 }
mas01mj@732 255
mas01mj@732 256 // If we have an even number of backslashes, that means this is the ending quote
mas01mj@732 257 if ( backspaceCount % 2 == 0 )
mas01mj@732 258 {
mas01mj@732 259 break;
mas01mj@732 260 }
mas01mj@732 261
mas01mj@732 262 // At this point, the quote was determined to be part of an escape sequence
mas01mj@732 263 // so we need to move past the quote index to look for the next one
mas01mj@732 264 quoteIndex++;
mas01mj@732 265 }
mas01mj@732 266 else // There are no more quotes in the string and we haven't found the end yet
mas01mj@732 267 {
mas01mj@732 268 parseError( "Unterminated string literal" );
mas01mj@732 269 }
mas01mj@732 270 } while ( true );
mas01mj@732 271
mas01mj@732 272 // Unescape the string
mas01mj@732 273 // the token for the string we'll try to read
mas01mj@732 274 var token:JSONToken = new JSONToken();
mas01mj@732 275 token.type = JSONTokenType.STRING;
mas01mj@732 276 // Attach resulting string to the token to return it
mas01mj@732 277 token.value = unescapeString( jsonString.substr( loc, quoteIndex - loc ) );
mas01mj@732 278
mas01mj@732 279 // Move past the closing quote in the input string. This updates the next
mas01mj@732 280 // character in the input stream to be the character one after the closing quote
mas01mj@732 281 loc = quoteIndex + 1;
mas01mj@732 282 nextChar();
mas01mj@732 283
mas01mj@732 284 return token;
mas01mj@732 285 }
mas01mj@732 286
mas01mj@732 287 /**
mas01mj@732 288 * Convert all JavaScript escape characters into normal characters
mas01mj@732 289 *
mas01mj@732 290 * @param input The input string to convert
mas01mj@732 291 * @return Original string with escape characters replaced by real characters
mas01mj@732 292 */
mas01mj@732 293 public function unescapeString( input:String ):String
mas01mj@732 294 {
mas01mj@732 295 // Issue #104 - If the string contains any unescaped control characters, this
mas01mj@732 296 // is an error in strict mode
mas01mj@732 297 if ( strict && controlCharsRegExp.test( input ) )
mas01mj@732 298 {
mas01mj@732 299 parseError( "String contains unescaped control character (0x00-0x1F)" );
mas01mj@732 300 }
mas01mj@732 301
mas01mj@732 302 var result:String = "";
mas01mj@732 303 var backslashIndex:int = 0;
mas01mj@732 304 var nextSubstringStartPosition:int = 0;
mas01mj@732 305 var len:int = input.length;
mas01mj@732 306 do
mas01mj@732 307 {
mas01mj@732 308 // Find the next backslash in the input
mas01mj@732 309 backslashIndex = input.indexOf( '\\', nextSubstringStartPosition );
mas01mj@732 310
mas01mj@732 311 if ( backslashIndex >= 0 )
mas01mj@732 312 {
mas01mj@732 313 result += input.substr( nextSubstringStartPosition, backslashIndex - nextSubstringStartPosition );
mas01mj@732 314
mas01mj@732 315 // Move past the backslash and next character (all escape sequences are
mas01mj@732 316 // two characters, except for \u, which will advance this further)
mas01mj@732 317 nextSubstringStartPosition = backslashIndex + 2;
mas01mj@732 318
mas01mj@732 319 // Check the next character so we know what to escape
mas01mj@732 320 var afterBackslashIndex:int = backslashIndex + 1;
mas01mj@732 321 var escapedChar:String = input.charAt( afterBackslashIndex );
mas01mj@732 322 switch ( escapedChar )
mas01mj@732 323 {
mas01mj@732 324 // Try to list the most common expected cases first to improve performance
mas01mj@732 325
mas01mj@732 326 case '"': result += '"'; break; // quotation mark
mas01mj@732 327 case '\\': result += '\\'; break; // reverse solidus
mas01mj@732 328 case 'n': result += '\n'; break; // newline
mas01mj@732 329 case 'r': result += '\r'; break; // carriage return
mas01mj@732 330 case 't': result += '\t'; break; // horizontal tab
mas01mj@732 331
mas01mj@732 332 // Convert a unicode escape sequence to it's character value
mas01mj@732 333 case 'u':
mas01mj@732 334
mas01mj@732 335 // Save the characters as a string we'll convert to an int
mas01mj@732 336 var hexValue:String = "";
mas01mj@732 337
mas01mj@732 338 // Make sure there are enough characters in the string leftover
mas01mj@732 339 if ( nextSubstringStartPosition + 4 > len )
mas01mj@732 340 {
mas01mj@732 341 parseError( "Unexpected end of input. Expecting 4 hex digits after \\u." );
mas01mj@732 342 }
mas01mj@732 343
mas01mj@732 344 // Try to find 4 hex characters
mas01mj@732 345 for ( var i:int = nextSubstringStartPosition; i < nextSubstringStartPosition + 4; i++ )
mas01mj@732 346 {
mas01mj@732 347 // get the next character and determine
mas01mj@732 348 // if it's a valid hex digit or not
mas01mj@732 349 var possibleHexChar:String = input.charAt( i );
mas01mj@732 350 if ( !isHexDigit( possibleHexChar ) )
mas01mj@732 351 {
mas01mj@732 352 parseError( "Excepted a hex digit, but found: " + possibleHexChar );
mas01mj@732 353 }
mas01mj@732 354
mas01mj@732 355 // Valid hex digit, add it to the value
mas01mj@732 356 hexValue += possibleHexChar;
mas01mj@732 357 }
mas01mj@732 358
mas01mj@732 359 // Convert hexValue to an integer, and use that
mas01mj@732 360 // integer value to create a character to add
mas01mj@732 361 // to our string.
mas01mj@732 362 result += String.fromCharCode( parseInt( hexValue, 16 ) );
mas01mj@732 363 // Move past the 4 hex digits that we just read
mas01mj@732 364 nextSubstringStartPosition += 4;
mas01mj@732 365 break;
mas01mj@732 366
mas01mj@732 367 case 'f': result += '\f'; break; // form feed
mas01mj@732 368 case '/': result += '/'; break; // solidus
mas01mj@732 369 case 'b': result += '\b'; break; // bell
mas01mj@732 370 default: result += '\\' + escapedChar; // Couldn't unescape the sequence, so just pass it through
mas01mj@732 371 }
mas01mj@732 372 }
mas01mj@732 373 else
mas01mj@732 374 {
mas01mj@732 375 // No more backslashes to replace, append the rest of the string
mas01mj@732 376 result += input.substr( nextSubstringStartPosition );
mas01mj@732 377 break;
mas01mj@732 378 }
mas01mj@732 379
mas01mj@732 380 } while ( nextSubstringStartPosition < len );
mas01mj@732 381
mas01mj@732 382 return result;
mas01mj@732 383 }
mas01mj@732 384
mas01mj@732 385 /**
mas01mj@732 386 * Attempts to read a number from the input string. Places
mas01mj@732 387 * the character location at the first character after the
mas01mj@732 388 * number.
mas01mj@732 389 *
mas01mj@732 390 * @return The JSONToken with the number value if a number could
mas01mj@732 391 * be read. Throws an error otherwise.
mas01mj@732 392 */
mas01mj@732 393 private function readNumber():JSONToken
mas01mj@732 394 {
mas01mj@732 395 // the string to accumulate the number characters
mas01mj@732 396 // into that we'll convert to a number at the end
mas01mj@732 397 var input:String = "";
mas01mj@732 398
mas01mj@732 399 // check for a negative number
mas01mj@732 400 if ( ch == '-' )
mas01mj@732 401 {
mas01mj@732 402 input += '-';
mas01mj@732 403 nextChar();
mas01mj@732 404 }
mas01mj@732 405
mas01mj@732 406 // the number must start with a digit
mas01mj@732 407 if ( !isDigit( ch ) )
mas01mj@732 408 {
mas01mj@732 409 parseError( "Expecting a digit" );
mas01mj@732 410 }
mas01mj@732 411
mas01mj@732 412 // 0 can only be the first digit if it
mas01mj@732 413 // is followed by a decimal point
mas01mj@732 414 if ( ch == '0' )
mas01mj@732 415 {
mas01mj@732 416 input += ch;
mas01mj@732 417 nextChar();
mas01mj@732 418
mas01mj@732 419 // make sure no other digits come after 0
mas01mj@732 420 if ( isDigit( ch ) )
mas01mj@732 421 {
mas01mj@732 422 parseError( "A digit cannot immediately follow 0" );
mas01mj@732 423 }
mas01mj@732 424 // unless we have 0x which starts a hex number, but this
mas01mj@732 425 // doesn't match JSON spec so check for not strict mode.
mas01mj@732 426 else if ( !strict && ch == 'x' )
mas01mj@732 427 {
mas01mj@732 428 // include the x in the input
mas01mj@732 429 input += ch;
mas01mj@732 430 nextChar();
mas01mj@732 431
mas01mj@732 432 // need at least one hex digit after 0x to
mas01mj@732 433 // be valid
mas01mj@732 434 if ( isHexDigit( ch ) )
mas01mj@732 435 {
mas01mj@732 436 input += ch;
mas01mj@732 437 nextChar();
mas01mj@732 438 }
mas01mj@732 439 else
mas01mj@732 440 {
mas01mj@732 441 parseError( "Number in hex format require at least one hex digit after \"0x\"" );
mas01mj@732 442 }
mas01mj@732 443
mas01mj@732 444 // consume all of the hex values
mas01mj@732 445 while ( isHexDigit( ch ) )
mas01mj@732 446 {
mas01mj@732 447 input += ch;
mas01mj@732 448 nextChar();
mas01mj@732 449 }
mas01mj@732 450 }
mas01mj@732 451 }
mas01mj@732 452 else
mas01mj@732 453 {
mas01mj@732 454 // read numbers while we can
mas01mj@732 455 while ( isDigit( ch ) )
mas01mj@732 456 {
mas01mj@732 457 input += ch;
mas01mj@732 458 nextChar();
mas01mj@732 459 }
mas01mj@732 460 }
mas01mj@732 461
mas01mj@732 462 // check for a decimal value
mas01mj@732 463 if ( ch == '.' )
mas01mj@732 464 {
mas01mj@732 465 input += '.';
mas01mj@732 466 nextChar();
mas01mj@732 467
mas01mj@732 468 // after the decimal there has to be a digit
mas01mj@732 469 if ( !isDigit( ch ) )
mas01mj@732 470 {
mas01mj@732 471 parseError( "Expecting a digit" );
mas01mj@732 472 }
mas01mj@732 473
mas01mj@732 474 // read more numbers to get the decimal value
mas01mj@732 475 while ( isDigit( ch ) )
mas01mj@732 476 {
mas01mj@732 477 input += ch;
mas01mj@732 478 nextChar();
mas01mj@732 479 }
mas01mj@732 480 }
mas01mj@732 481
mas01mj@732 482 // check for scientific notation
mas01mj@732 483 if ( ch == 'e' || ch == 'E' )
mas01mj@732 484 {
mas01mj@732 485 input += "e"
mas01mj@732 486 nextChar();
mas01mj@732 487 // check for sign
mas01mj@732 488 if ( ch == '+' || ch == '-' )
mas01mj@732 489 {
mas01mj@732 490 input += ch;
mas01mj@732 491 nextChar();
mas01mj@732 492 }
mas01mj@732 493
mas01mj@732 494 // require at least one number for the exponent
mas01mj@732 495 // in this case
mas01mj@732 496 if ( !isDigit( ch ) )
mas01mj@732 497 {
mas01mj@732 498 parseError( "Scientific notation number needs exponent value" );
mas01mj@732 499 }
mas01mj@732 500
mas01mj@732 501 // read in the exponent
mas01mj@732 502 while ( isDigit( ch ) )
mas01mj@732 503 {
mas01mj@732 504 input += ch;
mas01mj@732 505 nextChar();
mas01mj@732 506 }
mas01mj@732 507 }
mas01mj@732 508
mas01mj@732 509 // convert the string to a number value
mas01mj@732 510 var num:Number = Number( input );
mas01mj@732 511
mas01mj@732 512 if ( isFinite( num ) && !isNaN( num ) )
mas01mj@732 513 {
mas01mj@732 514 // the token for the number that we've read
mas01mj@732 515 var token:JSONToken = new JSONToken();
mas01mj@732 516 token.type = JSONTokenType.NUMBER;
mas01mj@732 517 token.value = num;
mas01mj@732 518 return token;
mas01mj@732 519 }
mas01mj@732 520 else
mas01mj@732 521 {
mas01mj@732 522 parseError( "Number " + num + " is not valid!" );
mas01mj@732 523 }
mas01mj@732 524
mas01mj@732 525 return null;
mas01mj@732 526 }
mas01mj@732 527
mas01mj@732 528 /**
mas01mj@732 529 * Reads the next character in the input
mas01mj@732 530 * string and advances the character location.
mas01mj@732 531 *
mas01mj@732 532 * @return The next character in the input string, or
mas01mj@732 533 * null if we've read past the end.
mas01mj@732 534 */
mas01mj@732 535 private function nextChar():String
mas01mj@732 536 {
mas01mj@732 537 return ch = jsonString.charAt( loc++ );
mas01mj@732 538 }
mas01mj@732 539
mas01mj@732 540 /**
mas01mj@732 541 * Advances the character location past any
mas01mj@732 542 * sort of white space and comments
mas01mj@732 543 */
mas01mj@732 544 private function skipIgnored():void
mas01mj@732 545 {
mas01mj@732 546 var originalLoc:int;
mas01mj@732 547
mas01mj@732 548 // keep trying to skip whitespace and comments as long
mas01mj@732 549 // as we keep advancing past the original location
mas01mj@732 550 do
mas01mj@732 551 {
mas01mj@732 552 originalLoc = loc;
mas01mj@732 553 skipWhite();
mas01mj@732 554 skipComments();
mas01mj@732 555 }
mas01mj@732 556 while ( originalLoc != loc );
mas01mj@732 557 }
mas01mj@732 558
mas01mj@732 559 /**
mas01mj@732 560 * Skips comments in the input string, either
mas01mj@732 561 * single-line or multi-line. Advances the character
mas01mj@732 562 * to the first position after the end of the comment.
mas01mj@732 563 */
mas01mj@732 564 private function skipComments():void
mas01mj@732 565 {
mas01mj@732 566 if ( ch == '/' )
mas01mj@732 567 {
mas01mj@732 568 // Advance past the first / to find out what type of comment
mas01mj@732 569 nextChar();
mas01mj@732 570 switch ( ch )
mas01mj@732 571 {
mas01mj@732 572 case '/': // single-line comment, read through end of line
mas01mj@732 573
mas01mj@732 574 // Loop over the characters until we find
mas01mj@732 575 // a newline or until there's no more characters left
mas01mj@732 576 do
mas01mj@732 577 {
mas01mj@732 578 nextChar();
mas01mj@732 579 }
mas01mj@732 580 while ( ch != '\n' && ch != '' )
mas01mj@732 581
mas01mj@732 582 // move past the \n
mas01mj@732 583 nextChar();
mas01mj@732 584
mas01mj@732 585 break;
mas01mj@732 586
mas01mj@732 587 case '*': // multi-line comment, read until closing */
mas01mj@732 588
mas01mj@732 589 // move past the opening *
mas01mj@732 590 nextChar();
mas01mj@732 591
mas01mj@732 592 // try to find a trailing */
mas01mj@732 593 while ( true )
mas01mj@732 594 {
mas01mj@732 595 if ( ch == '*' )
mas01mj@732 596 {
mas01mj@732 597 // check to see if we have a closing /
mas01mj@732 598 nextChar();
mas01mj@732 599 if ( ch == '/')
mas01mj@732 600 {
mas01mj@732 601 // move past the end of the closing */
mas01mj@732 602 nextChar();
mas01mj@732 603 break;
mas01mj@732 604 }
mas01mj@732 605 }
mas01mj@732 606 else
mas01mj@732 607 {
mas01mj@732 608 // move along, looking if the next character is a *
mas01mj@732 609 nextChar();
mas01mj@732 610 }
mas01mj@732 611
mas01mj@732 612 // when we're here we've read past the end of
mas01mj@732 613 // the string without finding a closing */, so error
mas01mj@732 614 if ( ch == '' )
mas01mj@732 615 {
mas01mj@732 616 parseError( "Multi-line comment not closed" );
mas01mj@732 617 }
mas01mj@732 618 }
mas01mj@732 619
mas01mj@732 620 break;
mas01mj@732 621
mas01mj@732 622 // Can't match a comment after a /, so it's a parsing error
mas01mj@732 623 default:
mas01mj@732 624 parseError( "Unexpected " + ch + " encountered (expecting '/' or '*' )" );
mas01mj@732 625 }
mas01mj@732 626 }
mas01mj@732 627
mas01mj@732 628 }
mas01mj@732 629
mas01mj@732 630
mas01mj@732 631 /**
mas01mj@732 632 * Skip any whitespace in the input string and advances
mas01mj@732 633 * the character to the first character after any possible
mas01mj@732 634 * whitespace.
mas01mj@732 635 */
mas01mj@732 636 private function skipWhite():void
mas01mj@732 637 {
mas01mj@732 638 // As long as there are spaces in the input
mas01mj@732 639 // stream, advance the current location pointer
mas01mj@732 640 // past them
mas01mj@732 641 while ( isWhiteSpace( ch ) )
mas01mj@732 642 {
mas01mj@732 643 nextChar();
mas01mj@732 644 }
mas01mj@732 645
mas01mj@732 646 }
mas01mj@732 647
mas01mj@732 648 /**
mas01mj@732 649 * Determines if a character is whitespace or not.
mas01mj@732 650 *
mas01mj@732 651 * @return True if the character passed in is a whitespace
mas01mj@732 652 * character
mas01mj@732 653 */
mas01mj@732 654 private function isWhiteSpace( ch:String ):Boolean
mas01mj@732 655 {
mas01mj@732 656 // Check for the whitespace defined in the spec
mas01mj@732 657 if ( ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' )
mas01mj@732 658 {
mas01mj@732 659 return true;
mas01mj@732 660 }
mas01mj@732 661 // If we're not in strict mode, we also accept non-breaking space
mas01mj@732 662 else if ( !strict && ch.charCodeAt( 0 ) == 160 )
mas01mj@732 663 {
mas01mj@732 664 return true;
mas01mj@732 665 }
mas01mj@732 666
mas01mj@732 667 return false;
mas01mj@732 668 }
mas01mj@732 669
mas01mj@732 670 /**
mas01mj@732 671 * Determines if a character is a digit [0-9].
mas01mj@732 672 *
mas01mj@732 673 * @return True if the character passed in is a digit
mas01mj@732 674 */
mas01mj@732 675 private function isDigit( ch:String ):Boolean
mas01mj@732 676 {
mas01mj@732 677 return ( ch >= '0' && ch <= '9' );
mas01mj@732 678 }
mas01mj@732 679
mas01mj@732 680 /**
mas01mj@732 681 * Determines if a character is a hex digit [0-9A-Fa-f].
mas01mj@732 682 *
mas01mj@732 683 * @return True if the character passed in is a hex digit
mas01mj@732 684 */
mas01mj@732 685 private function isHexDigit( ch:String ):Boolean
mas01mj@732 686 {
mas01mj@732 687 return ( isDigit( ch ) || ( ch >= 'A' && ch <= 'F' ) || ( ch >= 'a' && ch <= 'f' ) );
mas01mj@732 688 }
mas01mj@732 689
mas01mj@732 690 /**
mas01mj@732 691 * Raises a parsing error with a specified message, tacking
mas01mj@732 692 * on the error location and the original string.
mas01mj@732 693 *
mas01mj@732 694 * @param message The message indicating why the error occurred
mas01mj@732 695 */
mas01mj@732 696 public function parseError( message:String ):void
mas01mj@732 697 {
mas01mj@732 698 throw new JSONParseError( message, loc, jsonString );
mas01mj@732 699 }
mas01mj@732 700 }
mas01mj@732 701
mas01mj@732 702 }