Project

General

Profile

Statistics
| Branch: | Revision:

root / docs / www / colonyscout / internal / includes / uploadify / com / adobe / serialization / json / JSONTokenizer.as @ f59acf11

History | View | Annotate | Download (14.6 KB)

1
/*
2
  Copyright (c) 2008, Adobe Systems Incorporated
3
  All rights reserved.
4

    
5
  Redistribution and use in source and binary forms, with or without 
6
  modification, are permitted provided that the following conditions are
7
  met:
8

    
9
  * Redistributions of source code must retain the above copyright notice, 
10
    this list of conditions and the following disclaimer.
11
  
12
  * Redistributions in binary form must reproduce the above copyright
13
    notice, this list of conditions and the following disclaimer in the 
14
    documentation and/or other materials provided with the distribution.
15
  
16
  * Neither the name of Adobe Systems Incorporated nor the names of its 
17
    contributors may be used to endorse or promote products derived from 
18
    this software without specific prior written permission.
19

    
20
  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
21
  IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
22
  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23
  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
24
  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25
  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26
  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28
  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29
  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
*/
32

    
33
package com.adobe.serialization.json {
34

    
35
	public class JSONTokenizer {
36
	
37
		/** The object that will get parsed from the JSON string */
38
		private var obj:Object;
39
		
40
		/** The JSON string to be parsed */
41
		private var jsonString:String;
42
		
43
		/** The current parsing location in the JSON string */
44
		private var loc:int;
45
		
46
		/** The current character in the JSON string during parsing */
47
		private var ch:String;
48
		
49
		/**
50
		 * Constructs a new JSONDecoder to parse a JSON string 
51
		 * into a native object.
52
		 *
53
		 * @param s The JSON string to be converted
54
		 *		into a native object
55
		 */
56
		public function JSONTokenizer( s:String ) {
57
			jsonString = s;
58
			loc = 0;
59
			
60
			// prime the pump by getting the first character
61
			nextChar();
62
		}
63
		
64
		/**
65
		 * Gets the next token in the input sting and advances
66
		* the character to the next character after the token
67
		 */
68
		public function getNextToken():JSONToken {
69
			var token:JSONToken = new JSONToken();
70
			
71
			// skip any whitespace / comments since the last 
72
			// token was read
73
			skipIgnored();
74
						
75
			// examine the new character and see what we have...
76
			switch ( ch ) {
77
				
78
				case '{':
79
					token.type = JSONTokenType.LEFT_BRACE;
80
					token.value = '{';
81
					nextChar();
82
					break
83
					
84
				case '}':
85
					token.type = JSONTokenType.RIGHT_BRACE;
86
					token.value = '}';
87
					nextChar();
88
					break
89
					
90
				case '[':
91
					token.type = JSONTokenType.LEFT_BRACKET;
92
					token.value = '[';
93
					nextChar();
94
					break
95
					
96
				case ']':
97
					token.type = JSONTokenType.RIGHT_BRACKET;
98
					token.value = ']';
99
					nextChar();
100
					break
101
				
102
				case ',':
103
					token.type = JSONTokenType.COMMA;
104
					token.value = ',';
105
					nextChar();
106
					break
107
					
108
				case ':':
109
					token.type = JSONTokenType.COLON;
110
					token.value = ':';
111
					nextChar();
112
					break;
113
					
114
				case 't': // attempt to read true
115
					var possibleTrue:String = "t" + nextChar() + nextChar() + nextChar();
116
					
117
					if ( possibleTrue == "true" ) {
118
						token.type = JSONTokenType.TRUE;
119
						token.value = true;
120
						nextChar();
121
					} else {
122
						parseError( "Expecting 'true' but found " + possibleTrue );
123
					}
124
					
125
					break;
126
					
127
				case 'f': // attempt to read false
128
					var possibleFalse:String = "f" + nextChar() + nextChar() + nextChar() + nextChar();
129
					
130
					if ( possibleFalse == "false" ) {
131
						token.type = JSONTokenType.FALSE;
132
						token.value = false;
133
						nextChar();
134
					} else {
135
						parseError( "Expecting 'false' but found " + possibleFalse );
136
					}
137
					
138
					break;
139
					
140
				case 'n': // attempt to read null
141
				
142
					var possibleNull:String = "n" + nextChar() + nextChar() + nextChar();
143
					
144
					if ( possibleNull == "null" ) {
145
						token.type = JSONTokenType.NULL;
146
						token.value = null;
147
						nextChar();
148
					} else {
149
						parseError( "Expecting 'null' but found " + possibleNull );
150
					}
151
					
152
					break;
153
					
154
				case '"': // the start of a string
155
					token = readString();
156
					break;
157
					
158
				default: 
159
					// see if we can read a number
160
					if ( isDigit( ch ) || ch == '-' ) {
161
						token = readNumber();
162
					} else if ( ch == '' ) {
163
						// check for reading past the end of the string
164
						return null;
165
					} else {						
166
						// not sure what was in the input string - it's not
167
						// anything we expected
168
						parseError( "Unexpected " + ch + " encountered" );
169
					}
170
			}
171
			
172
			return token;
173
		}
174
		
175
		/**
176
		 * Attempts to read a string from the input string.  Places
177
		 * the character location at the first character after the
178
		 * string.  It is assumed that ch is " before this method is called.
179
		 *
180
		 * @return the JSONToken with the string value if a string could
181
		 *		be read.  Throws an error otherwise.
182
		 */
183
		private function readString():JSONToken {
184
			// the token for the string we'll try to read
185
			var token:JSONToken = new JSONToken();
186
			token.type = JSONTokenType.STRING;
187
			
188
			// the string to store the string we'll try to read
189
			var string:String = "";
190
			
191
			// advance past the first "
192
			nextChar();
193
			
194
			while ( ch != '"' && ch != '' ) {
195
								
196
				// unescape the escape sequences in the string
197
				if ( ch == '\\' ) {
198
					
199
					// get the next character so we know what
200
					// to unescape
201
					nextChar();
202
					
203
					switch ( ch ) {
204
						
205
						case '"': // quotation mark
206
							string += '"';
207
							break;
208
						
209
						case '/':	// solidus
210
							string += "/";
211
							break;
212
							
213
						case '\\':	// reverse solidus
214
							string += '\\';
215
							break;
216
							
217
						case 'b':	// bell
218
							string += '\b';
219
							break;
220
							
221
						case 'f':	// form feed
222
							string += '\f';
223
							break;
224
							
225
						case 'n':	// newline
226
							string += '\n';
227
							break;
228
							
229
						case 'r':	// carriage return
230
							string += '\r';
231
							break;
232
							
233
						case 't':	// horizontal tab
234
							string += '\t'
235
							break;
236
						
237
						case 'u':
238
							// convert a unicode escape sequence
239
							// to it's character value - expecting
240
							// 4 hex digits
241
							
242
							// save the characters as a string we'll convert to an int
243
							var hexValue:String = "";
244
							
245
							// try to find 4 hex characters
246
							for ( var i:int = 0; i < 4; i++ ) {
247
								// get the next character and determine
248
								// if it's a valid hex digit or not
249
								if ( !isHexDigit( nextChar() ) ) {
250
									parseError( " Excepted a hex digit, but found: " + ch );
251
								}
252
								// valid, add it to the value
253
								hexValue += ch;
254
							}
255
							
256
							// convert hexValue to an integer, and use that
257
							// integrer value to create a character to add
258
							// to our string.
259
							string += String.fromCharCode( parseInt( hexValue, 16 ) );
260
							
261
							break;
262
					
263
						default:
264
							// couldn't unescape the sequence, so just
265
							// pass it through
266
							string += '\\' + ch;
267
						
268
					}
269
					
270
				} else {
271
					// didn't have to unescape, so add the character to the string
272
					string += ch;
273
					
274
				}
275
				
276
				// move to the next character
277
				nextChar();
278
				
279
			}
280
			
281
			// we read past the end of the string without closing it, which
282
			// is a parse error
283
			if ( ch == '' ) {
284
				parseError( "Unterminated string literal" );
285
			}
286
			
287
			// move past the closing " in the input string
288
			nextChar();
289
			
290
			// attach to the string to the token so we can return it
291
			token.value = string;
292
			
293
			return token;
294
		}
295
		
296
		/**
297
		 * Attempts to read a number from the input string.  Places
298
		 * the character location at the first character after the
299
		 * number.
300
		 * 
301
		 * @return The JSONToken with the number value if a number could
302
		 * 		be read.  Throws an error otherwise.
303
		 */
304
		private function readNumber():JSONToken {
305
			// the token for the number we'll try to read
306
			var token:JSONToken = new JSONToken();
307
			token.type = JSONTokenType.NUMBER;
308
			
309
			// the string to accumulate the number characters
310
			// into that we'll convert to a number at the end
311
			var input:String = "";
312
			
313
			// check for a negative number
314
			if ( ch == '-' ) {
315
				input += '-';
316
				nextChar();
317
			}
318
			
319
			// the number must start with a digit
320
			if ( !isDigit( ch ) )
321
			{
322
				parseError( "Expecting a digit" );
323
			}
324
			
325
			// 0 can only be the first digit if it
326
			// is followed by a decimal point
327
			if ( ch == '0' )
328
			{
329
				input += ch;
330
				nextChar();
331
				
332
				// make sure no other digits come after 0
333
				if ( isDigit( ch ) )
334
				{
335
					parseError( "A digit cannot immediately follow 0" );
336
				}
337
// Commented out - this should only be available when "strict" is false
338
//				// unless we have 0x which starts a hex number\
339
//				else if ( ch == 'x' )
340
//				{
341
//					// include the x in the input
342
//					input += ch;
343
//					nextChar();
344
//					
345
//					// need at least one hex digit after 0x to
346
//					// be valid
347
//					if ( isHexDigit( ch ) )
348
//					{
349
//						input += ch;
350
//						nextChar();
351
//					}
352
//					else
353
//					{
354
//						parseError( "Number in hex format require at least one hex digit after \"0x\"" );	
355
//					}
356
//					
357
//					// consume all of the hex values
358
//					while ( isHexDigit( ch ) )
359
//					{
360
//						input += ch;
361
//						nextChar();
362
//					}
363
//				}
364
			}
365
			else
366
			{
367
				// read numbers while we can
368
				while ( isDigit( ch ) ) {
369
					input += ch;
370
					nextChar();
371
				}
372
			}
373
			
374
			// check for a decimal value
375
			if ( ch == '.' ) {
376
				input += '.';
377
				nextChar();
378
				
379
				// after the decimal there has to be a digit
380
				if ( !isDigit( ch ) )
381
				{
382
					parseError( "Expecting a digit" );
383
				}
384
				
385
				// read more numbers to get the decimal value
386
				while ( isDigit( ch ) ) {
387
					input += ch;
388
					nextChar();
389
				}
390
			}
391
			
392
			// check for scientific notation
393
			if ( ch == 'e' || ch == 'E' )
394
			{
395
				input += "e"
396
				nextChar();
397
				// check for sign
398
				if ( ch == '+' || ch == '-' )
399
				{
400
					input += ch;
401
					nextChar();
402
				}
403
				
404
				// require at least one number for the exponent
405
				// in this case
406
				if ( !isDigit( ch ) )
407
				{
408
					parseError( "Scientific notation number needs exponent value" );
409
				}
410
							
411
				// read in the exponent
412
				while ( isDigit( ch ) )
413
				{
414
					input += ch;
415
					nextChar();
416
				}
417
			}
418
			
419
			// convert the string to a number value
420
			var num:Number = Number( input );
421
			
422
			if ( isFinite( num ) && !isNaN( num ) ) {
423
				token.value = num;
424
				return token;
425
			} else {
426
				parseError( "Number " + num + " is not valid!" );
427
			}
428
            return null;
429
		}
430

    
431
		/**
432
		 * Reads the next character in the input
433
		 * string and advances the character location.
434
		 *
435
		 * @return The next character in the input string, or
436
		 *		null if we've read past the end.
437
		 */
438
		private function nextChar():String {
439
			return ch = jsonString.charAt( loc++ );
440
		}
441
		
442
		/**
443
		 * Advances the character location past any
444
		 * sort of white space and comments
445
		 */
446
		private function skipIgnored():void
447
		{
448
			var originalLoc:int;
449
			
450
			// keep trying to skip whitespace and comments as long
451
			// as we keep advancing past the original location 
452
			do
453
			{
454
				originalLoc = loc;
455
				skipWhite();
456
				skipComments();
457
			}
458
			while ( originalLoc != loc );
459
		}
460
		
461
		/**
462
		 * Skips comments in the input string, either
463
		 * single-line or multi-line.  Advances the character
464
		 * to the first position after the end of the comment.
465
		 */
466
		private function skipComments():void {
467
			if ( ch == '/' ) {
468
				// Advance past the first / to find out what type of comment
469
				nextChar();
470
				switch ( ch ) {
471
					case '/': // single-line comment, read through end of line
472
						
473
						// Loop over the characters until we find
474
						// a newline or until there's no more characters left
475
						do {
476
							nextChar();
477
						} while ( ch != '\n' && ch != '' )
478
						
479
						// move past the \n
480
						nextChar();
481
						
482
						break;
483
					
484
					case '*': // multi-line comment, read until closing */
485

    
486
						// move past the opening *
487
						nextChar();
488
						
489
						// try to find a trailing */
490
						while ( true ) {
491
							if ( ch == '*' ) {
492
								// check to see if we have a closing /
493
								nextChar();
494
								if ( ch == '/') {
495
									// move past the end of the closing */
496
									nextChar();
497
									break;
498
								}
499
							} else {
500
								// move along, looking if the next character is a *
501
								nextChar();
502
							}
503
							
504
							// when we're here we've read past the end of 
505
							// the string without finding a closing */, so error
506
							if ( ch == '' ) {
507
								parseError( "Multi-line comment not closed" );
508
							}
509
						}
510

    
511
						break;
512
					
513
					// Can't match a comment after a /, so it's a parsing error
514
					default:
515
						parseError( "Unexpected " + ch + " encountered (expecting '/' or '*' )" );
516
				}
517
			}
518
			
519
		}
520
		
521
		
522
		/**
523
		 * Skip any whitespace in the input string and advances
524
		 * the character to the first character after any possible
525
		 * whitespace.
526
		 */
527
		private function skipWhite():void {
528
			
529
			// As long as there are spaces in the input 
530
			// stream, advance the current location pointer
531
			// past them
532
			while ( isWhiteSpace( ch ) ) {
533
				nextChar();
534
			}
535
			
536
		}
537
		
538
		/**
539
		 * Determines if a character is whitespace or not.
540
		 *
541
		 * @return True if the character passed in is a whitespace
542
		 *	character
543
		 */
544
		private function isWhiteSpace( ch:String ):Boolean {
545
			return ( ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' );
546
		}
547
		
548
		/**
549
		 * Determines if a character is a digit [0-9].
550
		 *
551
		 * @return True if the character passed in is a digit
552
		 */
553
		private function isDigit( ch:String ):Boolean {
554
			return ( ch >= '0' && ch <= '9' );
555
		}
556
		
557
		/**
558
		 * Determines if a character is a digit [0-9].
559
		 *
560
		 * @return True if the character passed in is a digit
561
		 */
562
		private function isHexDigit( ch:String ):Boolean {
563
			// get the uppercase value of ch so we only have
564
			// to compare the value between 'A' and 'F'
565
			var uc:String = ch.toUpperCase();
566
			
567
			// a hex digit is a digit of A-F, inclusive ( using
568
			// our uppercase constraint )
569
			return ( isDigit( ch ) || ( uc >= 'A' && uc <= 'F' ) );
570
		}
571
	
572
		/**
573
		 * Raises a parsing error with a specified message, tacking
574
		 * on the error location and the original string.
575
		 *
576
		 * @param message The message indicating why the error occurred
577
		 */
578
		public function parseError( message:String ):void {
579
			throw new JSONParseError( message, loc, jsonString );
580
		}
581
	}
582
	
583
}