JSDOC/ScopeParser.js
[gnome.introspection-doc-generator] / JSDOC / ScopeParser.js
1 //<Script type="text/javascript">
2
3 Scope = imports['JSDOC/Scope.js'].Scope;
4
5 /**
6 * Scope stuff
7
8 * // FIXME - I need this to do next() without doccomments..
9
10
11
12 * Need to make this alot simpler...
13
14 * so debugging is possible.
15
16
17 * at present it just runs along the stream and finds stuff then calls parseExpr .. etc,,
18
19
20 * It would be better to parse blocks of code rather than the whole stream..
21
22
23
24
25
26 */
27
28 ScopeParser = function(ts) {
29     this.ts = ts; // {TokenStream}
30     this.warnings = [];
31     this.scopes = [];
32     this.indexedScopes = {};
33     this.timer = new Date() * 1;
34     this.debug = false;
35 }
36
37 // list of keywords that should not be used in object literals.
38 ScopeParser.idents = [
39         "break",         
40         "case",          
41         "continue",     
42         "default",      
43         "delete",       
44         "do",            
45         "else",         
46         "export",       
47         "false",        
48         "for",          
49         "function",     
50         "if",           
51         "import",       
52         "in",           
53         "new",          
54         "null",         
55         "return",       
56         "switch",       
57         "this",         
58         "true",         
59         "typeof",       
60         "var",          
61         "void",         
62         "while",        
63         "with",         
64
65         "catch",        
66         "class",        
67         "const",        
68         "debugger",     
69         "enum",         
70         "extends",      
71         "finally",      
72         "super",        
73         "throw",         
74         "try",          
75
76         "abstract",     
77         "boolean",      
78         "byte",         
79         "char",         
80         "double",       
81         "final",        
82         "float",        
83         "goto",         
84         "implements", 
85         "instanceof",
86         "int",           
87         "interface",     
88         "long",          
89         "native",       
90         "package",      
91         "private",      
92         "protected",     
93         "public",        
94         "short",        
95         "static",       
96         "synchronized",  
97         "throws",        
98         "transient",     
99                 "include",       
100                 "undefined"
101 ];
102
103
104 ScopeParser.prototype = {
105     timer: 0,
106     timerPrint: function (str) {
107         var ntime = new Date() * 1;
108         var tdif =  ntime -this.timer;
109         this.timer = ntime;
110         var pref = '';
111         if (tdif > 100) { //slower ones..
112             pref = '***';
113         }
114         println(pref+'['+tdif+']'+str);
115         
116     },
117     warn: function(s) {
118         //print('****************' + s);
119         this.warnings.push(s);
120         //println("WARNING:" + htmlescape(s) + "<BR>");
121     },
122     // defaults should not be initialized here =- otherwise they get duped on new, rather than initalized..
123     warnings : false,
124     ts : false,
125     scopes : false,
126     global : false,
127     mode : "", //"BUILDING_SYMBOL_TREE",
128     braceNesting : 0,
129     indexedScopes : false,
130     munge: true,
131
132
133
134
135
136     buildSymbolTree : function()
137     {
138         //println("<PRE>");
139         
140         this.ts.rewind();
141         this.braceNesting = 0;
142         this.scopes = [];
143         
144         
145         
146         
147         this.globalScope = new  Scope(-1, false, -1, '');
148         indexedScopes = { 0 : this.globalScope };
149         
150         this.mode = 'BUILDING_SYMBOL_TREE';
151         this.parseScope(this.globalScope);
152         
153         //print("---------------END PASS 1 ---------------- ");
154         
155     },
156     mungeSymboltree : function()
157     {
158
159         if (!this.munge) {
160             return;
161         }
162
163         // One problem with obfuscation resides in the use of undeclared
164         // and un-namespaced global symbols that are 3 characters or less
165         // in length. Here is an example:
166         //
167         //     var declaredGlobalVar;
168         //
169         //     function declaredGlobalFn() {
170         //         var localvar;
171         //         localvar = abc; // abc is an undeclared global symbol
172         //     }
173         //
174         // In the example above, there is a slim chance that localvar may be
175         // munged to 'abc', conflicting with the undeclared global symbol
176         // abc, creating a potential bug. The following code detects such
177         // global symbols. This must be done AFTER the entire file has been
178         // parsed, and BEFORE munging the symbol tree. Note that declaring
179         // extra symbols in the global scope won't hurt.
180         //
181         // Note: Since we go through all the tokens to do this, we also use
182         // the opportunity to count how many times each identifier is used.
183
184         this.ts.rewind();
185         this.braceNesting = 0;
186         this.scopes= [];
187         this.mode = 'PASS2_SYMBOL_TREE';
188         
189         //println("MUNGING?");
190         
191         this.parseScope(this.globalScope);
192         this.globalScope.munge();
193     },
194
195
196     log : function(str)
197     {
198         print ("                    ".substring(0, this.braceNesting*2) + str);
199         
200         //println("<B>LOG:</B>" + htmlescape(str) + "<BR/>\n");
201     },
202     logR : function(str)
203     {
204             //println("<B>LOG:</B>" + str + "<BR/>");
205     },
206
207      
208     
209
210
211     parseScope : function(scope) // parse a token stream..
212     {
213         //this.timerPrint("parseScope EnterScope"); 
214         //this.log(">>> ENTER SCOPE" + this.scopes.length);
215         var symbol;
216         var token;
217         
218         var identifier;
219
220         var expressionBraceNesting = this.braceNesting + 0;
221         
222         var parensNesting = 0;
223         
224         var isObjectLitAr = [ false ];
225         var isInObjectLitAr;
226         this.scopes.push(scope);
227        
228         //var scopeIndent = ''; 
229         //this.scopes.forEach(function() {
230         //    scopeIndent += '   '; 
231         //});
232         //print(scopeIndent + ">> ENTER SCOPE");
233         
234         
235         
236         
237         token = this.ts.lookTok(1);
238         while (token) {
239           //  this.timerPrint("parseScope AFTER lookT: " + token.toString()); 
240             //this.dumpToken(token , this.scopes, this.braceNesting);
241             //print('SCOPE:' + token.toString());
242             //this.log(token.data);
243             if (token.type == 'NAME') {
244             //    print('*' + token.data);
245             }
246             switch(token.type + '.' + token.name) {
247                 case "KEYW.VAR":
248                 case "KEYW.CONST": // not really relivant as it's only mozzy that does this.
249                     print('SCOPE-VAR:' + token.toString());
250                     var vstart = this.ts.cursor +1;
251                     
252                     //this.log("parseScope GOT VAR/CONST : " + token.toString()); 
253                     while (true) {
254                         token = this.ts.nextTok();
255                         !this.debug|| print( token.toString());
256                       
257                         if (!token) { // can return false at EOF!
258                             break;
259                         }
260                         if (token.name == "VAR" || token.data == ',') { // kludge..
261                             continue;
262                         }
263                         //this.logR("parseScope GOT VAR  : <B>" + token.toString() + "</B>"); 
264                         if (token.type !="NAME") {
265                             for(var i = Math.max(this.ts.cursor-10,0); i < this.ts.cursor+1; i++) {
266                                 print(this.ts.tokens[i].toString());
267                             }
268                             
269                             print( "var without ident");
270                             Seed.quit()
271                         }
272                         
273
274                         if (this.mode == "BUILDING_SYMBOL_TREE") {
275                             identifier = scope.getIdentifier(token.data) ;
276                             
277                             if (identifier == false) {
278                                 scope.declareIdentifier(token.data, token);
279                             } else {
280                                 token.identifier = identifier;
281                                 this.warn("(SCOPE) The variable " + token.data  + ' (line:' + token.line + ")  has already been declared in the same scope...");
282                             }
283                         }
284
285                         token = this.ts.nextTok();
286                         !this.debug|| print(token.toString());
287                         /*
288                         assert token.getType() == Token.SEMI ||
289                                 token.getType() == Token.ASSIGN ||
290                                 token.getType() == Token.COMMA ||
291                                 token.getType() == Token.IN;
292                         */
293                         if (token.name == "IN") {
294                             break;
295                         } else {
296                             //var bn = this.braceNesting;
297                             var bn = this.braceNesting;
298                             this.parseExpression();
299                             this.braceNesting = bn;
300                             //this.braceNesting = bn;
301                             //this.logR("parseScope DONE  : <B>ParseExpression</B> - tok is:" + this.ts.lookT(0).toString()); 
302                             
303                             token = this.ts.lookTok(1);
304                             !this.debug|| print("AFTER EXP: " + token.toString());
305                             if (token.data == ';') {
306                                 break;
307                             }
308                         }
309                     }
310                     
311                     //print("VAR:")
312                     //this.ts.dump(vstart , this.ts.cursor);
313                     
314                     break;
315                 case "KEYW.FUNCTION":
316                     print('SCOPE-FUNC:' + token.toString());
317                     //println("<i>"+token.data+"</i>");
318                      var bn = this.braceNesting;
319                     this.parseFunctionDeclaration();
320                      this.braceNesting = bn;
321                     break;
322
323                 case "PUNC.LEFT_CURLY": // {
324                     print('SCOPE-CURLY:' + JSON.stringify(token,null,4));
325                     //println("<i>"+token.data+"</i>");
326                     
327                     if (token.props) {
328                         var curTS = this.ts;
329                         for (var prop in token.props) {
330                             if (token.props[prop].val.data == 'function') {
331                                 // parse a function..
332                                 this.parseFunctProp(token.props[prop]);
333                                 continue;
334                             }
335                             // key value..
336                             
337                             this.ts = new TokenStream(token.props[prop].val);
338                             this.parseScope(scope);
339                             
340                         }
341                         this.ts = curTS;
342                         
343                         // it's an object literal..
344                         // the values could be replaced..
345                         
346                     }
347                     print("NOT PROPS"); Seed.quit();
348                     
349                     isObjectLitAr.push(false);
350                     this.braceNesting++;
351                     
352                     //print(">>>>>> OBJLIT PUSH(false)" + this.braceNesting);
353                     break;
354 /*
355                 case "PUNC.RIGHT_CURLY": // }
356                     //println("<i>"+token.data+"</i>");
357                     this.braceNesting--;
358                     isObjectLitAr.pop();
359                     //print(">>>>>> OBJLIT POP"+ this.braceNesting);
360                         //assert braceNesting >= scope.getBra ceNesting();
361                     
362                     if (this.braceNesting < expressionBraceNesting) {
363                         var ls = this.scopes.pop();
364                         ls.getUsedSymbols();
365                         // eat symbol if we are currently at { 
366                         if (this.ts.look(0).data == '{') {
367                             this.ts.nextTok();
368                         }
369                         
370                         print("<<<<<<<EXIT SCOPE" +this.scopes.length);
371                         return;
372                     }
373                     break;
374 */
375                 case "KEYW.WITH":
376                     print('SCOPE-WITH:' + token.toString());
377                     //println("<i>"+token.data+"</i>");   
378                     if (this.mode == "BUILDING_SYMBOL_TREE") {
379                         // Inside a 'with' block, it is impossible to figure out
380                         // statically whether a symbol is a local variable or an
381                         // object member. As a consequence, the only thing we can
382                         // do is turn the obfuscation off for the highest scope
383                         // containing the 'with' block.
384                         this.protectScopeFromObfuscation(scope);
385                         this.warn("Using 'with' is not recommended." + (this.munge ? " Moreover, using 'with' reduces the level of compression!" : ""), true);
386                     }
387                     break;
388
389                 case "KEYW.CATCH":
390                     print('SCOPE-CATCH:' + token.toString());
391                     //println("<i>"+token.data+"</i>");
392                     this.parseCatch();
393                     break;
394                 /*
395                 case Token.SPECIALCOMMENT:
396                         if (mode == BUILDING_SYMBOL_TREE) {
397                             protectScopeFromObfuscation(scope);
398                             this.warn("Using JScript conditional comments is not recommended." + (munge ? " Moreover, using JScript conditional comments reduces the level of compression." : ""), true);
399                         }
400                         break;
401                 */
402                 
403                 case "STRN.DOUBLE_QUOTE": // used for object lit detection..
404                 case "STRN.SINGLE_QUOTE":
405                     print('SCOPE-STRING:' + token.toString());
406                     //println("<i>"+token.data+"</i>");
407
408                     if (this.ts.lookTok(-1).data == '{' && this.ts.lookTok(1).data == ':') {
409                         // then we are in an object lit.. -> we need to flag the brace as such...
410                         isObjectLitAr.pop();
411                         isObjectLitAr.push(true);
412                         //print(">>>>>> OBJLIT REPUSH(true)");
413                     }
414                     isInObjectLitAr = isObjectLitAr[isObjectLitAr.length-1];
415                     
416                     if (isInObjectLitAr &&  this.ts.lookTok(1).data == ':' &&
417                         ( this.ts.lookTok(-1).data == '{'  ||  this.ts.lookTok(-1).data == ':' )) {
418                         // see if we can replace..
419                         // remove the quotes..
420                         // should do a bit more checking!!!! (what about wierd char's in the string..
421                         var str = token.data.substring(1,token.data.length-1);
422                         if (/^[a-z_]+$/i.test(str) && ScopeParser.idents.indexOf(str) < 0) {
423                             token.outData = str;
424                         }
425                         
426                          
427                         
428                     }
429                     
430                     
431                     
432                     break;
433                 
434                 case "NAME.NAME":
435                     print('SCOPE-NAME:' + token.toString());
436                     //print("DEAL WITH NAME:");
437                     // got identifier..
438                     
439                     // look for  { ** : <- indicates obj literal.. ** this could occur with numbers ..
440                     if ((this.ts.lookTok(-1).data == "{") && (this.ts.lookTok(1).data == ":")) {
441                         isObjectLitAr.pop();
442                         isObjectLitAr.push(true);
443                         //print(">>>>>> OBJLIT REPUSH(true)");
444                         //println("<i>"+token.data+"</i>");
445                         break;
446                     }
447                    // print("DEAL WITH obj lit:");
448                     isInObjectLitAr = isObjectLitAr[isObjectLitAr.length-1];
449                     
450                     if (isInObjectLitAr && (this.ts.lookTok(1).data == ":") && (this.ts.lookTok(-1).data == ",")) {
451                         // skip, it's an object lit key..
452                         //println("<i>"+token.data+"</i>");
453                         break;
454                     }
455                     
456                     
457                     // skip anyting with "." before it..!!
458                      
459                     if (this.ts.lookTok(-1).data == ".") {
460                         // skip, it's an object prop.
461                         //println("<i>"+token.data+"</i>");
462                         break;
463                     }
464                     //print("SYMBOL: " + token.toString());
465                     
466                     symbol = token.data;
467                     if (this.mode == 'PASS2_SYMBOL_TREE') {
468                         
469                         //println("GOT IDENT: -2 : " + this.ts.lookT(-2).toString() + " <BR> ..... -1 :  " +  this.ts.lookT(-1).toString() + " <BR> "); 
470                         
471                         //print ("MUNGE?" + symbol);
472                         
473                         //println("GOT IDENT: <B>" + symbol + "</B><BR/>");
474                              
475                             //println("GOT IDENT (2): <B>" + symbol + "</B><BR/>");
476                         identifier = this.getIdentifier(symbol, scope);
477                         
478                         if (identifier == false) {
479 // BUG!find out where builtin is defined...
480                             if (symbol.length <= 3 &&  Scope.builtin.indexOf(symbol) < 0) {
481                                 // Here, we found an undeclared and un-namespaced symbol that is
482                                 // 3 characters or less in length. Declare it in the global scope.
483                                 // We don't need to declare longer symbols since they won't cause
484                                 // any conflict with other munged symbols.
485                                 this.globalScope.declareIdentifier(symbol, token);
486                                 this.warn("Found an undeclared symbol: " + symbol + ' (line:' + token.line + ')', true);
487                             }
488                             
489                             //println("GOT IDENT IGNORE(3): <B>" + symbol + "</B><BR/>");
490                         } else {
491                             token.identifier = identifier;
492                             identifier.refcount++;
493                         }
494                     }   
495                     
496                     break;
497                     //println("<B>SID</B>");
498                 default:
499                     if (token.type != 'KEYW') {
500                         break;
501                     }
502                     print('SCOPE-KEYW:' + token.toString());
503                    // print("Check eval:");
504                 
505                     symbol = token.data;
506                     
507                      if (this.mode == 'BUILDING_SYMBOL_TREE') {
508
509                         if (symbol == "eval") {
510                             // look back one and see if we can find a comment!!!
511                             if (this.ts.look(-1).type == "COMM") {
512                                 // look for eval:var:noreplace\n
513                                 var _t = this;
514                                 this.ts.look(-1).data.replace(/eval:var:([a-z_]+)/ig, function(m, a) {
515                                     
516                                     var hi = _t.getIdentifier(a, scope);
517                                    // println("PROTECT "+a+" from munge" + (hi ? "FOUND" : "MISSING"));
518                                     if (hi) {
519                                      //   println("PROTECT "+a+" from munge");
520                                         hi.toMunge = false;
521                                     }
522                                     
523                                 });
524                                 
525                                 
526                             } else {
527                                 
528                             
529                                 this.protectScopeFromObfuscation(scope);
530                                 this.warn("Using 'eval' is not recommended. (use  eval:var:noreplace in comments to optimize) " + (this.munge ? " Moreover, using 'eval' reduces the level of compression!" : ""), true);
531                             }
532
533                         }
534
535                     }
536                     break;
537                 
538                 
539             } // end switch
540             
541             
542             //print("parseScope TOK : " + token.toString()); 
543             token = this.ts.nextTok();
544             //if (this.ts.nextT()) break;
545             
546         }
547         //print("<<<<<<<EXIT SCOPE ERR?" +this.scopes.length);
548     },
549
550     expN : 0,
551     parseExpression : function() {
552
553         // Parse the expression until we encounter a comma or a semi-colon
554         // in the same brace nesting, bracket nesting and paren nesting.
555         // Parse functions if any...
556         //println("<i>EXP</i><BR/>");
557         !this.debug || print("PARSE EXPR");
558         this.expN++;
559          
560         // for printing stuff..
561        
562         
563         
564         var symbol;
565         var token;
566         var currentScope;
567         var identifier;
568
569         var expressionBraceNesting = this.braceNesting + 0;
570         var bracketNesting = 0;
571         var parensNesting = 0;
572         var isInObjectLitAr;
573         var isObjectLitAr = [ false ];
574         
575         currentScope = this.scopes[this.scopes.length-1];
576             
577         var scopeIndent = ''; 
578         this.scopes.forEach(function() {
579             scopeIndent += '   '; 
580         });
581         //print(scopeIndent + ">> ENTER EXPRESSION" + this.expN);
582         while (token = this.ts.lookTok()) {
583      
584
585             
586            /*
587             // moved out of loop?
588            currentScope = this.scopes[this.scopes.length-1];
589             
590             var scopeIndent = ''; 
591             this.scopes.forEach(function() {
592                 scopeIndent += '   '; 
593             });
594            */ 
595            
596            //this.dumpToken(token,  this.scopes, this.braceNesting );
597            //print('EXP' + this.expN + ':' + token.toString());
598             
599             
600             //println("<i>"+token.data+"</i>");
601             //this.log("EXP:" + token.data);
602             switch (token.type) {
603                 case 'PUNC':
604                     switch(token.data) {
605                          
606                         case ';':
607                         case ',':
608                             if (this.braceNesting == expressionBraceNesting &&
609                                     bracketNesting == 0 &&
610                                     parensNesting == 0) {
611                                 print(scopeIndent + "<< EXIT EXPRESSION");
612                                 this.expN--;
613                                 return;
614                             }
615                             break;
616
617                        
618
619                         case '{': //Token.LC:
620                             isObjectLitAr.push(false);
621                             
622                             this.braceNesting++;
623                             ///print(">>>>> EXP PUSH(false)"+this.braceNesting);
624                             break;
625
626                         case '}': //Token.RC:
627                             this.braceNesting--;
628                             isObjectLitAr.pop();
629                             //print(">>>>> EXP POP" + this.braceNesting);    
630                            // assert braceNesting >= expressionBraceNesting;
631                             break;
632
633                         case '[': //Token.LB:
634                             bracketNesting++;
635                             break;
636
637                         case ']': //Token.RB:
638                             bracketNesting--;
639                             break;
640
641                         case '(': //Token.LP:
642                             parensNesting++;
643                             break;
644
645                         case ')': //Token.RP:
646                             parensNesting--;
647                             break;
648                     }
649                     break;
650                     
651                 case 'STRN': // used for object lit detection..
652                     if (this.ts.lookTok(-1).data == "{" && this.ts.lookTok(1).data == ":" ) {
653                         // then we are in an object lit.. -> we need to flag the brace as such...
654                         isObjectLitAr.pop();
655                         isObjectLitAr.push(true);
656                         //print(">>>>> EXP PUSH(true)");
657                     }
658                     
659                     
660                      
661                     isInObjectLitAr = isObjectLitAr[isObjectLitAr.length-1];
662                     if (isInObjectLitAr &&  this.ts.lookTok(1).data == ":"  &&
663                         ( this.ts.lookTok(-1).data == "{"  ||  this.ts.lookTok(-1).data == "," )) {
664                         // see if we can replace..
665                         // remove the quotes..
666                         var str = token.data.substring(1,token.data.length-1);
667                         if (/^[a-z_]+$/i.test(str) && ScopeParser.idents.indexOf(str) < 0) {
668                             token.outData = str;
669                         }
670                         
671                          
672                         
673                     }
674                     
675                     break;
676                 
677                       
678              
679                 case 'NAME':
680                
681                     symbol = token.data;
682                     //print("in NAME = " + token.toString());
683                     //print("in NAME 0: " + this.ts.look(0).toString());
684                     //print("in NAME 2: " + this.ts.lookTok(2).toString());
685                     if (this.ts.look(0).data == "{"  && this.ts.lookTok(2).data == ":") {
686                         // then we are in an object lit.. -> we need to flag the brace as such...
687                         isObjectLitAr.pop();
688                         isObjectLitAr.push(true);
689                          //print(">>>>> EXP  PUSH(true)");
690                         break;
691                     }
692                     
693                     isInObjectLitAr = isObjectLitAr[isObjectLitAr.length-1];
694                     //print ("isInObjectLitAr : " + isInObjectLitAr + ' ' + token.toString());
695                     
696                     if (isInObjectLitAr && this.ts.lookTok(0).data == "," && this.ts.lookTok(2).data == ":") {
697                         break;
698                     }
699                     //print(this.ts.lookTok(0).data);
700                     if (this.ts.lookTok(0).data == ".") {
701                         //skip '.'
702                         break;
703                     }
704                     
705                      if (this.mode == 'PASS2_SYMBOL_TREE') {
706
707                         identifier = this.getIdentifier(symbol, currentScope);
708                         //println("<B>??</B>");
709                         if (identifier == false) {
710
711                             if (symbol.length <= 3 &&  Scope.builtin.indexOf(symbol) < 0) {
712                                 // Here, we found an undeclared and un-namespaced symbol that is
713                                 // 3 characters or less in length. Declare it in the global scope.
714                                 // We don't need to declare longer symbols since they won't cause
715                                 // any conflict with other munged symbols.
716                                 this.globalScope.declareIdentifier(symbol, token);
717                                 this.warn("Found an undeclared symbol: " + symbol + ' (line:' + token.line + ')', true);
718                             } else {
719                                 //println("undeclared")
720                             }
721                             
722                             
723                         } else {
724                             //println("<B>++</B>");
725                             token.identifier = identifier;
726                             identifier.refcount++;
727                         }
728                         
729                     }
730                     break;
731                     
732                     
733                     
734                     
735                     //println("<B>EID</B>");
736                  case 'KEYW':   
737                  
738                     if (token.name == "FUNCTION") {
739                         
740                         this.parseFunctionDeclaration();
741                         break;
742                     }
743                
744                     
745              
746                     symbol = token.data;
747                     if (this.mode == 'BUILDING_SYMBOL_TREE') {
748
749                         if (symbol == "eval") {
750                             if (this.ts.look(-1).type == 'COMM') {
751                                 // look for eval:var:noreplace\n
752                                 var _t = this;
753                                 this.ts.look(-1).data.replace(/eval:var:([a-z]+)/ig, function(m, a) {
754                                     var hi = _t.getIdentifier(a, currentScope);
755                                    //println("PROTECT "+a+" from munge" + (hi ? "FOUND" : "MISSING"));
756                                     if (hi) {
757                                       //  println("PROTECT "+a+" from munge");
758                                         hi.toMunge = false;
759                                     }
760                                     
761                                     
762                                 });
763                                 
764                             } else {
765                                 this.protectScopeFromObfuscation(currentScope);
766                                 this.warn("Using 'eval' is not recommended." + (this.munge ? " Moreover, using 'eval' reduces the level of compression!" : ""), true);
767                             }
768                             
769
770                         }
771                         break;
772                     } 
773                    
774             }
775             if (!this.ts.nextTok()) break;
776         }
777         print(scopeIndent + "<< EXIT EXPRESSION");
778         this.expN--;
779     },
780
781
782     parseCatch : function() {
783
784         var symbol;
785         var token;
786         var currentScope;
787         var identifier;
788
789         //token = getToken(-1);
790         //assert token.getType() == Token.CATCH;
791         token = this.ts.nextTok();
792         //assert token.getType() == Token.LP; (
793         token = this.ts.nextTok();
794         //assert token.getType() == Token.NAME;
795         
796         symbol = token.data;
797         currentScope = this.scopes[this.scopes.length-1];
798
799         if (this.mode == 'BUILDING_SYMBOL_TREE') {
800             // We must declare the exception identifier in the containing function
801             // scope to avoid errors related to the obfuscation process. No need to
802             // display a warning if the symbol was already declared here...
803             currentScope.declareIdentifier(symbol, token);
804         } else {
805             //?? why inc the refcount?? - that should be set when building the tree???
806             identifier = this.getIdentifier(symbol, currentScope);
807             identifier.refcount++;
808         }
809
810         token = this.ts.nextTok();
811         //assert token.getType() == Token.RP; // )
812     },
813     
814     parseFunctionDeclaration : function() 
815     {
816         //print("PARSE FUNCTION");
817         var symbol;
818         var token;
819         var currentScope  = false; 
820         var fnScope = false;
821         var identifier;
822         var b4braceNesting = this.braceNesting + 0;
823         
824         //this.logR("<B>PARSING FUNCTION</B>");
825         currentScope = this.scopes[this.scopes.length-1];
826
827         token = this.ts.nextTok();
828         if (token.type == "NAME") {
829             if (this.mode == 'BUILDING_SYMBOL_TREE') {
830                 // Get the name of the function and declare it in the current scope.
831                 symbol = token.data;
832                 if (currentScope.getIdentifier(symbol) != false) {
833                     this.warn("The function " + symbol + " has already been declared in the same scope...", true);
834                 }
835                 currentScope.declareIdentifier(symbol,token);
836             }
837             token =  this.ts.nextTok();
838         }
839
840         //assert token.getType() == Token.LP;
841         if (this.mode == 'BUILDING_SYMBOL_TREE') {
842             fnScope = new Scope(this.braceNesting, currentScope, token.n, '');
843             
844             //println("STORING SCOPE" + this.ts.cursor);
845             
846             this.indexedScopes[this.ts.cursor] = fnScope;
847             
848         } else {
849             //qln("FETCHING SCOPE" + this.ts.cursor);
850             fnScope = this.indexedScopes[this.ts.cursor];
851           
852         }
853         
854         // Parse function arguments.
855         var argpos = 0;
856         while (this.ts.lookTok().data != ')') { //(token = consumeToken()).getType() != Token.RP) {
857             token = this.ts.nextTok();
858            // print ("FUNC ARGS: " + token.toString())
859             //assert token.getType() == Token.NAME ||
860             //        token.getType() == Token.COMMA;
861             if (token.type == 'NAME' && this.mode == 'BUILDING_SYMBOL_TREE') {
862                 symbol = token.data;
863                 identifier = fnScope.declareIdentifier(symbol,token);
864                 if (symbol == "$super" && argpos == 0) {
865                     // Exception for Prototype 1.6...
866                     identifier.preventMunging();
867                 }
868                 argpos++;
869             }
870         }
871
872         token = this.ts.nextTok();
873         //print(token.toString());
874         // assert token.getType() == Token.LC;
875         this.braceNesting++;
876
877         token = this.ts.nextTok();
878         //print(token.toString());
879      
880
881         this.parseScope(fnScope);
882         // now pop it off the stack!!!
883        
884         this.braceNesting = b4braceNesting;
885         //print("ENDFN -1: " + this.ts.lookTok(-1).toString());
886         //print("ENDFN 0: " + this.ts.lookTok(0).toString());
887         //print("ENDFN 1: " + this.ts.lookTok(1).toString());
888     },
889     
890     protectScopeFromObfuscation : function(scope) {
891             //assert scope != null;
892         
893         if (scope == this.globalScope) {
894             // The global scope does not get obfuscated,
895             // so we don't need to worry about it...
896             return;
897         }
898
899         // Find the highest local scope containing the specified scope.
900         while (scope && scope.parent != this.globalScope) {
901             scope = scope.parent;
902         }
903
904         //assert scope.getParentScope() == globalScope;
905         scope.preventMunging();
906     },
907     
908     getIdentifier: function(symbol, scope) {
909         var identifier;
910         while (scope != false) {
911             identifier = scope.getIdentifier(symbol);
912             //println("ScopeParser.getIdentgetUsedSymbols("+symbol+")=" + scope.getUsedSymbols().join(','));
913             if (identifier) {
914                 return identifier;
915             }
916             scope = scope.parent;
917         }
918         return false;
919     }
920 };