]> git.mjollnir.org Git - moodle.git/blob
5d9546e07f1ed588fac2e6ab1f0c92c9154b343c
[moodle.git] /
1 /*
2 Adobe Systems Incorporated(r) Source Code License Agreement
3 Copyright(c) 2005 Adobe Systems Incorporated. All rights reserved.
4         
5 Please read this Source Code License Agreement carefully before using
6 the source code.
7         
8 Adobe Systems Incorporated grants to you a perpetual, worldwide, non-exclusive,
9 no-charge, royalty-free, irrevocable copyright license, to reproduce,
10 prepare derivative works of, publicly display, publicly perform, and
11 distribute this source code and such derivative works in source or
12 object code form without any attribution requirements.
13         
14 The name "Adobe Systems Incorporated" must not be used to endorse or promote products
15 derived from the source code without prior written permission.
16         
17 You agree to indemnify, hold harmless and defend Adobe Systems Incorporated from and
18 against any loss, damage, claims or lawsuits, including attorney's
19 fees that arise or result from your use or distribution of the source
20 code.
21         
22 THIS SOURCE CODE IS PROVIDED "AS IS" AND "WITH ALL FAULTS", WITHOUT
23 ANY TECHNICAL SUPPORT OR ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
24 BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ALSO, THERE IS NO WARRANTY OF
26 NON-INFRINGEMENT, TITLE OR QUIET ENJOYMENT. IN NO EVENT SHALL MACROMEDIA
27 OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
28 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
29 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
30 OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
31 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
32 OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOURCE CODE, EVEN IF
33 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 */
35
36 package com.adobe.serialization.json {
37
38       /**
39        * @private
40        */
41         public class JSONTokenizer {
42         
43                 /** The object that will get parsed from the JSON string */
44                 private var obj:Object;
45                 
46                 /** The JSON string to be parsed */
47                 private var jsonString:String;
48                 
49                 /** The current parsing location in the JSON string */
50                 private var loc:int;
51                 
52                 /** The current character in the JSON string during parsing */
53                 private var ch:String;
54                 
55                 /**
56                  * Constructs a new JSONDecoder to parse a JSON string 
57                  * into a native object.
58                  *
59                  * @param s The JSON string to be converted
60                  *              into a native object
61                  */
62                 public function JSONTokenizer( s:String ) {
63                         jsonString = s;
64                         loc = 0;
65                         
66                         // prime the pump by getting the first character
67                         nextChar();
68                 }
69                 
70                 /**
71                  * Gets the next token in the input sting and advances
72                 * the character to the next character after the token
73                  */
74                 public function getNextToken():JSONToken {
75                         var token:JSONToken = new JSONToken();
76                         
77                         // skip any whitespace / comments since the last 
78                         // token was read
79                         skipIgnored();
80                                                 
81                         // examine the new character and see what we have...
82                         switch ( ch ) {
83                                 
84                                 case '{':
85                                         token.type = JSONTokenType.LEFT_BRACE;
86                                         token.value = '{';
87                                         nextChar();
88                                         break
89                                         
90                                 case '}':
91                                         token.type = JSONTokenType.RIGHT_BRACE;
92                                         token.value = '}';
93                                         nextChar();
94                                         break
95                                         
96                                 case '[':
97                                         token.type = JSONTokenType.LEFT_BRACKET;
98                                         token.value = '[';
99                                         nextChar();
100                                         break
101                                         
102                                 case ']':
103                                         token.type = JSONTokenType.RIGHT_BRACKET;
104                                         token.value = ']';
105                                         nextChar();
106                                         break
107                                 
108                                 case ',':
109                                         token.type = JSONTokenType.COMMA;
110                                         token.value = ',';
111                                         nextChar();
112                                         break
113                                         
114                                 case ':':
115                                         token.type = JSONTokenType.COLON;
116                                         token.value = ':';
117                                         nextChar();
118                                         break;
119                                         
120                                 case 't': // attempt to read true
121                                         var possibleTrue:String = "t" + nextChar() + nextChar() + nextChar();
122                                         
123                                         if ( possibleTrue == "true" ) {
124                                                 token.type = JSONTokenType.TRUE;
125                                                 token.value = true;
126                                                 nextChar();
127                                         } else {
128                                                 parseError( "Expecting 'true' but found " + possibleTrue );
129                                         }
130                                         
131                                         break;
132                                         
133                                 case 'f': // attempt to read false
134                                         var possibleFalse:String = "f" + nextChar() + nextChar() + nextChar() + nextChar();
135                                         
136                                         if ( possibleFalse == "false" ) {
137                                                 token.type = JSONTokenType.FALSE;
138                                                 token.value = false;
139                                                 nextChar();
140                                         } else {
141                                                 parseError( "Expecting 'false' but found " + possibleFalse );
142                                         }
143                                         
144                                         break;
145                                         
146                                 case 'n': // attempt to read null
147                                 
148                                         var possibleNull:String = "n" + nextChar() + nextChar() + nextChar();
149                                         
150                                         if ( possibleNull == "null" ) {
151                                                 token.type = JSONTokenType.NULL;
152                                                 token.value = null;
153                                                 nextChar();
154                                         } else {
155                                                 parseError( "Expecting 'null' but found " + possibleNull );
156                                         }
157                                         
158                                         break;
159                                         
160                                 case '"': // the start of a string
161                                         token = readString();
162                                         break;
163                                         
164                                 default: 
165                                         // see if we can read a number
166                                         if ( isDigit( ch ) || ch == '-' ) {
167                                                 token = readNumber();
168                                         } else if ( ch == '' ) {
169                                                 // check for reading past the end of the string
170                                                 return null;
171                                         } else {                                                
172                                                 // not sure what was in the input string - it's not
173                                                 // anything we expected
174                                                 parseError( "Unexpected " + ch + " encountered" );
175                                         }
176                         }
177                         
178                         return token;
179                 }
180                 
181                 /**
182                  * Attempts to read a string from the input string.  Places
183                  * the character location at the first character after the
184                  * string.  It is assumed that ch is " before this method is called.
185                  *
186                  * @return the JSONToken with the string value if a string could
187                  *              be read.  Throws an error otherwise.
188                  */
189                 private function readString():JSONToken {
190                         // the token for the string we'll try to read
191                         var token:JSONToken = new JSONToken();
192                         token.type = JSONTokenType.STRING;
193                         
194                         // the string to store the string we'll try to read
195                         var string:String = "";
196                         
197                         // advance past the first "
198                         nextChar();
199                         
200                         while ( ch != '"' && ch != '' ) {
201                                                                 
202                                 // unescape the escape sequences in the string
203                                 if ( ch == '\\' ) {
204                                         
205                                         // get the next character so we know what
206                                         // to unescape
207                                         nextChar();
208                                         
209                                         switch ( ch ) {
210                                                 
211                                                 case '"': // quotation mark
212                                                         string += '"';
213                                                         break;
214                                                 
215                                                 case '/':       // solidus
216                                                         string += "/";
217                                                         break;
218                                                         
219                                                 case '\\':      // reverse solidus
220                                                         string += '\\';
221                                                         break;
222                                                         
223                                                 case 'b':       // bell
224                                                         string += '\b';
225                                                         break;
226                                                         
227                                                 case 'f':       // form feed
228                                                         string += '\f';
229                                                         break;
230                                                         
231                                                 case 'n':       // newline
232                                                         string += '\n';
233                                                         break;
234                                                         
235                                                 case 'r':       // carriage return
236                                                         string += '\r';
237                                                         break;
238                                                         
239                                                 case 't':       // horizontal tab
240                                                         string += '\t'
241                                                         break;
242                                                 
243                                                 case 'u':
244                                                         // convert a unicode escape sequence
245                                                         // to it's character value - expecting
246                                                         // 4 hex digits
247                                                         
248                                                         // save the characters as a string we'll convert to an int
249                                                         var hexValue:String = "";
250                                                         
251                                                         // try to find 4 hex characters
252                                                         for ( var i:int = 0; i < 4; i++ ) {
253                                                                 // get the next character and determine
254                                                                 // if it's a valid hex digit or not
255                                                                 if ( !isHexDigit( nextChar() ) ) {
256                                                                         parseError( " Excepted a hex digit, but found: " + ch );
257                                                                 }
258                                                                 // valid, add it to the value
259                                                                 hexValue += ch;
260                                                         }
261                                                         
262                                                         // convert hexValue to an integer, and use that
263                                                         // integrer value to create a character to add
264                                                         // to our string.
265                                                         string += String.fromCharCode( parseInt( hexValue, 16 ) );
266                                                         
267                                                         break;
268                                         
269                                                 default:
270                                                         // couldn't unescape the sequence, so just
271                                                         // pass it through
272                                                         string += '\\' + ch;
273                                                 
274                                         }
275                                         
276                                 } else {
277                                         // didn't have to unescape, so add the character to the string
278                                         string += ch;
279                                         
280                                 }
281                                 
282                                 // move to the next character
283                                 nextChar();
284                                 
285                         }
286                         
287                         // we read past the end of the string without closing it, which
288                         // is a parse error
289                         if ( ch == '' ) {
290                                 parseError( "Unterminated string literal" );
291                         }
292                         
293                         // move past the closing " in the input string
294                         nextChar();
295                         
296                         // attach to the string to the token so we can return it
297                         token.value = string;
298                         
299                         return token;
300                 }
301                 
302                 /**
303                  * Attempts to read a number from the input string.  Places
304                  * the character location at the first character after the
305                  * number.
306                  * 
307                  * @return The JSONToken with the number value if a number could
308                  *              be read.  Throws an error otherwise.
309                  */
310                 private function readNumber():JSONToken {
311                         // the token for the number we'll try to read
312                         var token:JSONToken = new JSONToken();
313                         token.type = JSONTokenType.NUMBER;
314                         
315                         // the string to accumulate the number characters
316                         // into that we'll convert to a number at the end
317                         var input:String = "";
318                         
319                         // check for a negative number
320                         if ( ch == '-' ) {
321                                 input += '-';
322                                 nextChar();
323                         }
324                         
325                         // read numbers while we can
326                         while ( isDigit( ch ) ) {
327                                 input += ch;
328                                 nextChar();
329                         }
330                         
331                         // check for a decimal value
332                         if ( ch == '.' ) {
333                                 input += '.';
334                                 nextChar();
335                                 // read more numbers to get the decimal value
336                                 while ( isDigit( ch ) ) {
337                                         input += ch;
338                                         nextChar();
339                                 }
340                         }
341                         
342                         //Application.application.show( "number = " + input );
343                         
344                         // conver the string to a number value
345                         var num:Number = Number( input );
346                         
347                         if ( isFinite( num ) ) {
348                                 token.value = num;
349                                 return token;
350                         } else {
351                                 parseError( "Number " + num + " is not valid!" );
352                         }
353             return null;
354                 }
355
356                 /**
357                  * Reads the next character in the input
358                  * string and advances the character location.
359                  *
360                  * @return The next character in the input string, or
361                  *              null if we've read past the end.
362                  */
363                 private function nextChar():String {
364                         return ch = jsonString.charAt( loc++ );
365                 }
366                 
367                 /**
368                  * Advances the character location past any
369                  * sort of white space and comments
370                  */
371                 private function skipIgnored():void {
372                         skipWhite();
373                         skipComments();
374                         skipWhite();
375                 }
376                 
377                 /**
378                  * Skips comments in the input string, either
379                  * single-line or multi-line.  Advances the character
380                  * to the first position after the end of the comment.
381                  */
382                 private function skipComments():void {
383                         if ( ch == '/' ) {
384                                 // Advance past the first / to find out what type of comment
385                                 nextChar();
386                                 switch ( ch ) {
387                                         case '/': // single-line comment, read through end of line
388                                                 
389                                                 // Loop over the characters until we find
390                                                 // a newline or until there's no more characters left
391                                                 do {
392                                                         nextChar();
393                                                 } while ( ch != '\n' && ch != '' )
394                                                 
395                                                 // move past the \n
396                                                 nextChar();
397                                                 
398                                                 break;
399                                         
400                                         case '*': // multi-line comment, read until closing */
401
402                                                 // move past the opening *
403                                                 nextChar();
404                                                 
405                                                 // try to find a trailing */
406                                                 while ( true ) {
407                                                         if ( ch == '*' ) {
408                                                                 // check to see if we have a closing /
409                                                                 nextChar();
410                                                                 if ( ch == '/') {
411                                                                         // move past the end of the closing */
412                                                                         nextChar();
413                                                                         break;
414                                                                 }
415                                                         } else {
416                                                                 // move along, looking if the next character is a *
417                                                                 nextChar();
418                                                         }
419                                                         
420                                                         // when we're here we've read past the end of 
421                                                         // the string without finding a closing */, so error
422                                                         if ( ch == '' ) {
423                                                                 parseError( "Multi-line comment not closed" );
424                                                         }
425                                                 }
426
427                                                 break;
428                                         
429                                         // Can't match a comment after a /, so it's a parsing error
430                                         default:
431                                                 parseError( "Unexpected " + ch + " encountered (expecting '/' or '*' )" );
432                                 }
433                         }
434                         
435                 }
436                 
437                 
438                 /**
439                  * Skip any whitespace in the input string and advances
440                  * the character to the first character after any possible
441                  * whitespace.
442                  */
443                 private function skipWhite():void {
444                         
445                         // As long as there are spaces in the input 
446                         // stream, advance the current location pointer
447                         // past them
448                         while ( isSpace( ch ) ) {
449                                 nextChar();
450                         }
451                         
452                 }
453                 
454                 /**
455                  * Determines if a character is whitespace or not.
456                  *
457                  * @return True if the character passed in is a whitespace
458                  *      character
459                  */
460                 private function isSpace( ch:String ):Boolean {
461                         return ( ch == ' ' || ch == '\t' );
462                 }
463                 
464                 /**
465                  * Determines if a character is a digit [0-9].
466                  *
467                  * @return True if the character passed in is a digit
468                  */
469                 private function isDigit( ch:String ):Boolean {
470                         return ( ch >= '0' && ch <= '9' );
471                 }
472                 
473                 /**
474                  * Determines if a character is a digit [0-9].
475                  *
476                  * @return True if the character passed in is a digit
477                  */
478                 private function isHexDigit( ch:String ):Boolean {
479                         // get the uppercase value of ch so we only have
480                         // to compare the value between 'A' and 'F'
481                         var uc:String = ch.toUpperCase();
482                         
483                         // a hex digit is a digit of A-F, inclusive ( using
484                         // our uppercase constraint )
485                         return ( isDigit( ch ) || ( uc >= 'A' && uc <= 'F' ) );
486                 }
487         
488                 /**
489                  * Raises a parsing error with a specified message, tacking
490                  * on the error location and the original string.
491                  *
492                  * @param message The message indicating why the error occurred
493                  */
494                 public function parseError( message:String ):void {
495                         throw new JSONParseError( message, loc, jsonString );
496                 }
497         }
498         
499 }