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                     
250                     
251                     //this.log("parseScope GOT VAR/CONST : " + token.toString()); 
252                     while (true) {
253                         token = this.ts.nextTok();
254                         !this.debug|| print( token.toString());
255                       
256                         if (!token) { // can return false at EOF!
257                             break;
258                         }
259                         if (token.name == "VAR" || token.data == ',') { // kludge..
260                             continue;
261                         }
262                         //this.logR("parseScope GOT VAR  : <B>" + token.toString() + "</B>"); 
263                         if (token.type !="NAME") {
264                             for(var i = Math.max(this.ts.cursor-10,0); i < this.ts.cursor+1; i++) {
265                                 print(this.ts.tokens[i].toString());
266                             }
267                             
268                             print( "var without ident");
269                             Seed.quit()
270                         }
271                         
272
273                         if (this.mode == "BUILDING_SYMBOL_TREE") {
274                             identifier = scope.getIdentifier(token.data) ;
275                             
276                             if (identifier == false) {
277                                 scope.declareIdentifier(token.data, token);
278                             } else {
279                                 token.identifier = identifier;
280                                 this.warn("(SCOPE) The variable " + token.data  + ' (line:' + token.line + ")  has already been declared in the same scope...");
281                             }
282                         }
283
284                         token = this.ts.nextTok();
285                         !this.debug|| print(token.toString());
286                         /*
287                         assert token.getType() == Token.SEMI ||
288                                 token.getType() == Token.ASSIGN ||
289                                 token.getType() == Token.COMMA ||
290                                 token.getType() == Token.IN;
291                         */
292                         if (token.name == "IN") {
293                             break;
294                         } else {
295                             //var bn = this.braceNesting;
296                             var bn = this.braceNesting;
297                             this.parseExpression();
298                             this.braceNesting = bn;
299                             //this.braceNesting = bn;
300                             //this.logR("parseScope DONE  : <B>ParseExpression</B> - tok is:" + this.ts.lookT(0).toString()); 
301                             
302                             token = this.ts.lookTok(1);
303                             !this.debug|| print("AFTER EXP: " + token.toString());
304                             if (token.data == ';') {
305                                 break;
306                             }
307                         }
308                     }
309                     break;
310                 case "KEYW.FUNCTION":
311                     //println("<i>"+token.data+"</i>");
312                      var bn = this.braceNesting;
313                     this.parseFunctionDeclaration();
314                      this.braceNesting = bn;
315                     break;
316
317                 case "PUNC.LEFT_CURLY": // {
318                     //println("<i>"+token.data+"</i>");
319                     isObjectLitAr.push(false);
320                     this.braceNesting++;
321                     
322                     //print(">>>>>> OBJLIT PUSH(false)" + this.braceNesting);
323                     break;
324
325                 case "PUNC.RIGHT_CURLY": // }
326                     //println("<i>"+token.data+"</i>");
327                     this.braceNesting--;
328                     isObjectLitAr.pop();
329                     //print(">>>>>> OBJLIT POP"+ this.braceNesting);
330                         //assert braceNesting >= scope.getBra ceNesting();
331                     
332                     if (this.braceNesting < expressionBraceNesting) {
333                         var ls = this.scopes.pop();
334                         ls.getUsedSymbols();
335                         // eat symbol if we are currently at { 
336                         if (this.ts.look(0).data == '{') {
337                             this.ts.nextTok();
338                         }
339                         
340                         print("<<<<<<<EXIT SCOPE" +this.scopes.length);
341                         return;
342                     }
343                     break;
344
345                 case "KEYW.WITH":
346                     //println("<i>"+token.data+"</i>");   
347                     if (this.mode == "BUILDING_SYMBOL_TREE") {
348                         // Inside a 'with' block, it is impossible to figure out
349                         // statically whether a symbol is a local variable or an
350                         // object member. As a consequence, the only thing we can
351                         // do is turn the obfuscation off for the highest scope
352                         // containing the 'with' block.
353                         this.protectScopeFromObfuscation(scope);
354                         this.warn("Using 'with' is not recommended." + (this.munge ? " Moreover, using 'with' reduces the level of compression!" : ""), true);
355                     }
356                     break;
357
358                 case "KEYW.CATCH":
359                     //println("<i>"+token.data+"</i>");
360                     this.parseCatch();
361                     break;
362                 /*
363                 case Token.SPECIALCOMMENT:
364                         if (mode == BUILDING_SYMBOL_TREE) {
365                             protectScopeFromObfuscation(scope);
366                             this.warn("Using JScript conditional comments is not recommended." + (munge ? " Moreover, using JScript conditional comments reduces the level of compression." : ""), true);
367                         }
368                         break;
369                 */
370                 
371                 case "STRN.DOUBLE_QUOTE": // used for object lit detection..
372                 case "STRN.SINGLE_QUOTE":
373                     //println("<i>"+token.data+"</i>");
374                     if (this.ts.lookTok(-1).data == '{' && this.ts.lookTok(1).data == ':') {
375                         // then we are in an object lit.. -> we need to flag the brace as such...
376                         isObjectLitAr.pop();
377                         isObjectLitAr.push(true);
378                         //print(">>>>>> OBJLIT REPUSH(true)");
379                     }
380                     isInObjectLitAr = isObjectLitAr[isObjectLitAr.length-1];
381                     
382                     if (isInObjectLitAr &&  this.ts.lookTok(1).data == ':' &&
383                         ( this.ts.lookTok(-1).data == '{'  ||  this.ts.lookTok(-1).data == ':' )) {
384                         // see if we can replace..
385                         // remove the quotes..
386                         // should do a bit more checking!!!! (what about wierd char's in the string..
387                         var str = token.data.substring(1,token.data.length-1);
388                         if (/^[a-z_]+$/i.test(str) && ScopeParser.idents.indexOf(str) < 0) {
389                             token.outData = str;
390                         }
391                         
392                          
393                         
394                     }
395                     
396                     
397                     
398                     break;
399                 
400                 case "NAME.NAME":
401                 
402                     //print("DEAL WITH NAME:");
403                     // got identifier..
404                     
405                     // look for  { ** : <- indicates obj literal.. ** this could occur with numbers ..
406                     if ((this.ts.lookTok(-1).data == "{") && (this.ts.lookTok(1).data == ":")) {
407                         isObjectLitAr.pop();
408                         isObjectLitAr.push(true);
409                         //print(">>>>>> OBJLIT REPUSH(true)");
410                         //println("<i>"+token.data+"</i>");
411                         break;
412                     }
413                    // print("DEAL WITH obj lit:");
414                     isInObjectLitAr = isObjectLitAr[isObjectLitAr.length-1];
415                     
416                     if (isInObjectLitAr && (this.ts.lookTok(1).data == ":") && (this.ts.lookTok(-1).data == ",")) {
417                         // skip, it's an object lit key..
418                         //println("<i>"+token.data+"</i>");
419                         break;
420                     }
421                     
422                     
423                     // skip anyting with "." before it..!!
424                      
425                     if (this.ts.lookTok(-1).data == ".") {
426                         // skip, it's an object prop.
427                         //println("<i>"+token.data+"</i>");
428                         break;
429                     }
430                     symbol = token.data;
431                     if (this.mode == 'PASS2_SYMBOL_TREE') {
432                         
433                         //println("GOT IDENT: -2 : " + this.ts.lookT(-2).toString() + " <BR> ..... -1 :  " +  this.ts.lookT(-1).toString() + " <BR> "); 
434                         
435                         //print ("MUNGE?" + symbol);
436                         
437                         //println("GOT IDENT: <B>" + symbol + "</B><BR/>");
438                              
439                             //println("GOT IDENT (2): <B>" + symbol + "</B><BR/>");
440                         identifier = this.getIdentifier(symbol, scope);
441                         
442                         if (identifier == false) {
443 // BUG!find out where builtin is defined...
444                             if (symbol.length <= 3 &&  Scope.builtin.indexOf(symbol) < 0) {
445                                 // Here, we found an undeclared and un-namespaced symbol that is
446                                 // 3 characters or less in length. Declare it in the global scope.
447                                 // We don't need to declare longer symbols since they won't cause
448                                 // any conflict with other munged symbols.
449                                 this.globalScope.declareIdentifier(symbol, token);
450                                 this.warn("Found an undeclared symbol: " + symbol + ' (line:' + token.line + ')', true);
451                             }
452                             
453                             //println("GOT IDENT IGNORE(3): <B>" + symbol + "</B><BR/>");
454                         } else {
455                             token.identifier = identifier;
456                             identifier.refcount++;
457                         }
458                     }   
459                     
460                     break;
461                     //println("<B>SID</B>");
462                 default:
463                     if (token.type != 'KEYW') {
464                         break;
465                     }
466                    // print("Check eval:");
467                 
468                     symbol = token.data;
469                     
470                      if (this.mode == 'BUILDING_SYMBOL_TREE') {
471
472                         if (symbol == "eval") {
473                             // look back one and see if we can find a comment!!!
474                             if (this.ts.look(-1).type == "COMM") {
475                                 // look for eval:var:noreplace\n
476                                 var _t = this;
477                                 this.ts.look(-1).data.replace(/eval:var:([a-z_]+)/ig, function(m, a) {
478                                     
479                                     var hi = _t.getIdentifier(a, scope);
480                                    // println("PROTECT "+a+" from munge" + (hi ? "FOUND" : "MISSING"));
481                                     if (hi) {
482                                      //   println("PROTECT "+a+" from munge");
483                                         hi.toMunge = false;
484                                     }
485                                     
486                                 });
487                                 
488                                 
489                             } else {
490                                 
491                             
492                                 this.protectScopeFromObfuscation(scope);
493                                 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);
494                             }
495
496                         }
497
498                     }
499                     break;
500                 
501                 
502             } // end switch
503             
504             
505             //print("parseScope TOK : " + token.toString()); 
506             token = this.ts.nextTok();
507             //if (this.ts.nextT()) break;
508             
509         }
510         //print("<<<<<<<EXIT SCOPE ERR?" +this.scopes.length);
511     },
512
513     expN : 0,
514     parseExpression : function() {
515
516         // Parse the expression until we encounter a comma or a semi-colon
517         // in the same brace nesting, bracket nesting and paren nesting.
518         // Parse functions if any...
519         //println("<i>EXP</i><BR/>");
520         !this.debug || print("PARSE EXPR");
521         this.expN++;
522          
523         // for printing stuff..
524        
525         
526         
527         var symbol;
528         var token;
529         var currentScope;
530         var identifier;
531
532         var expressionBraceNesting = this.braceNesting + 0;
533         var bracketNesting = 0;
534         var parensNesting = 0;
535         var isInObjectLitAr;
536         var isObjectLitAr = [ false ];
537         
538         currentScope = this.scopes[this.scopes.length-1];
539             
540         var scopeIndent = ''; 
541         this.scopes.forEach(function() {
542             scopeIndent += '   '; 
543         });
544         print(scopeIndent + ">> ENTER EXPRESSION" + this.expN);
545         while (token = this.ts.lookTok()) {
546      
547
548             
549            /*
550             // moved out of loop?
551            currentScope = this.scopes[this.scopes.length-1];
552             
553             var scopeIndent = ''; 
554             this.scopes.forEach(function() {
555                 scopeIndent += '   '; 
556             });
557            */ 
558            
559            //this.dumpToken(token,  this.scopes, this.braceNesting );
560            print('EXP' + this.expN + ':' + token.toString());
561             
562             
563             //println("<i>"+token.data+"</i>");
564             //this.log("EXP:" + token.data);
565             switch (token.type) {
566                 case 'PUNC':
567                     switch(token.data) {
568                          
569                         case ';':
570                         case ',':
571                             if (this.braceNesting == expressionBraceNesting &&
572                                     bracketNesting == 0 &&
573                                     parensNesting == 0) {
574                                 print(scopeIndent + "<< EXIT EXPRESSION");
575                                 this.expN--;
576                                 return;
577                             }
578                             break;
579
580                        
581
582                         case '{': //Token.LC:
583                             isObjectLitAr.push(false);
584                             
585                             this.braceNesting++;
586                             ///print(">>>>> EXP PUSH(false)"+this.braceNesting);
587                             break;
588
589                         case '}': //Token.RC:
590                             this.braceNesting--;
591                             isObjectLitAr.pop();
592                             //print(">>>>> EXP POP" + this.braceNesting);    
593                            // assert braceNesting >= expressionBraceNesting;
594                             break;
595
596                         case '[': //Token.LB:
597                             bracketNesting++;
598                             break;
599
600                         case ']': //Token.RB:
601                             bracketNesting--;
602                             break;
603
604                         case '(': //Token.LP:
605                             parensNesting++;
606                             break;
607
608                         case ')': //Token.RP:
609                             parensNesting--;
610                             break;
611                     }
612                     break;
613                     
614                 case 'STRN': // used for object lit detection..
615                     if (this.ts.lookTok(-1).data == "{" && this.ts.lookTok(1).data == ":" ) {
616                         // then we are in an object lit.. -> we need to flag the brace as such...
617                         isObjectLitAr.pop();
618                         isObjectLitAr.push(true);
619                         //print(">>>>> EXP PUSH(true)");
620                     }
621                     
622                     
623                      
624                     isInObjectLitAr = isObjectLitAr[isObjectLitAr.length-1];
625                     if (isInObjectLitAr &&  this.ts.lookTok(1).data == ":"  &&
626                         ( this.ts.lookTok(-1).data == "{"  ||  this.ts.lookTok(-1).data == "," )) {
627                         // see if we can replace..
628                         // remove the quotes..
629                         var str = token.data.substring(1,token.data.length-1);
630                         if (/^[a-z_]+$/i.test(str) && ScopeParser.idents.indexOf(str) < 0) {
631                             token.outData = str;
632                         }
633                         
634                          
635                         
636                     }
637                     
638                     break;
639                 
640                       
641              
642                 case 'NAME':
643                
644                     symbol = token.data;
645                     //print("in NAME = " + token.toString());
646                     //print("in NAME 0: " + this.ts.look(0).toString());
647                     //print("in NAME 2: " + this.ts.lookTok(2).toString());
648                     if (this.ts.look(0).data == "{"  && this.ts.lookTok(2).data == ":") {
649                         // then we are in an object lit.. -> we need to flag the brace as such...
650                         isObjectLitAr.pop();
651                         isObjectLitAr.push(true);
652                          //print(">>>>> EXP  PUSH(true)");
653                         break;
654                     }
655                     
656                     isInObjectLitAr = isObjectLitAr[isObjectLitAr.length-1];
657                     //print ("isInObjectLitAr : " + isInObjectLitAr + ' ' + token.toString());
658                     
659                     if (isInObjectLitAr && this.ts.lookTok(0).data == "," && this.ts.lookTok(2).data == ":") {
660                         break;
661                     }
662                     //print(this.ts.lookTok(0).data);
663                     if (this.ts.lookTok(0).data == ".") {
664                         //skip '.'
665                         break;
666                     }
667                     
668                      if (this.mode == 'PASS2_SYMBOL_TREE') {
669
670                         identifier = this.getIdentifier(symbol, currentScope);
671                         //println("<B>??</B>");
672                         if (identifier == false) {
673
674                             if (symbol.length <= 3 &&  Scope.builtin.indexOf(symbol) < 0) {
675                                 // Here, we found an undeclared and un-namespaced symbol that is
676                                 // 3 characters or less in length. Declare it in the global scope.
677                                 // We don't need to declare longer symbols since they won't cause
678                                 // any conflict with other munged symbols.
679                                 this.globalScope.declareIdentifier(symbol, token);
680                                 this.warn("Found an undeclared symbol: " + symbol + ' (line:' + token.line + ')', true);
681                             } else {
682                                 //println("undeclared")
683                             }
684                             
685                             
686                         } else {
687                             //println("<B>++</B>");
688                             token.identifier = identifier;
689                             identifier.refcount++;
690                         }
691                         
692                     }
693                     break;
694                     
695                     
696                     
697                     
698                     //println("<B>EID</B>");
699                  case 'KEYW':   
700                  
701                     if (token.name == "FUNCTION") {
702                         
703                         this.parseFunctionDeclaration();
704                         break;
705                     }
706                
707                     
708              
709                     symbol = token.data;
710                     if (this.mode == 'BUILDING_SYMBOL_TREE') {
711
712                         if (symbol == "eval") {
713                             if (this.ts.look(-1).type == 'COMM') {
714                                 // look for eval:var:noreplace\n
715                                 var _t = this;
716                                 this.ts.look(-1).data.replace(/eval:var:([a-z]+)/ig, function(m, a) {
717                                     var hi = _t.getIdentifier(a, currentScope);
718                                    //println("PROTECT "+a+" from munge" + (hi ? "FOUND" : "MISSING"));
719                                     if (hi) {
720                                       //  println("PROTECT "+a+" from munge");
721                                         hi.toMunge = false;
722                                     }
723                                     
724                                     
725                                 });
726                                 
727                             } else {
728                                 this.protectScopeFromObfuscation(currentScope);
729                                 this.warn("Using 'eval' is not recommended." + (this.munge ? " Moreover, using 'eval' reduces the level of compression!" : ""), true);
730                             }
731                             
732
733                         }
734                         break;
735                     } 
736                    
737             }
738             if (!this.ts.nextTok()) break;
739         }
740         print(scopeIndent + "<< EXIT EXPRESSION");
741         this.expN--;
742     },
743
744
745     parseCatch : function() {
746
747         var symbol;
748         var token;
749         var currentScope;
750         var identifier;
751
752         //token = getToken(-1);
753         //assert token.getType() == Token.CATCH;
754         token = this.ts.nextTok();
755         //assert token.getType() == Token.LP; (
756         token = this.ts.nextTok();
757         //assert token.getType() == Token.NAME;
758         
759         symbol = token.data;
760         currentScope = this.scopes[this.scopes.length-1];
761
762         if (this.mode == 'BUILDING_SYMBOL_TREE') {
763             // We must declare the exception identifier in the containing function
764             // scope to avoid errors related to the obfuscation process. No need to
765             // display a warning if the symbol was already declared here...
766             currentScope.declareIdentifier(symbol, token);
767         } else {
768             //?? why inc the refcount?? - that should be set when building the tree???
769             identifier = this.getIdentifier(symbol, currentScope);
770             identifier.refcount++;
771         }
772
773         token = this.ts.nextTok();
774         //assert token.getType() == Token.RP; // )
775     },
776     
777     parseFunctionDeclaration : function() 
778     {
779         print("PARSE FUNCTION");
780         var symbol;
781         var token;
782         var currentScope  = false; 
783         var fnScope = false;
784         var identifier;
785         var b4braceNesting = this.braceNesting + 0;
786         
787         //this.logR("<B>PARSING FUNCTION</B>");
788         currentScope = this.scopes[this.scopes.length-1];
789
790         token = this.ts.nextTok();
791         if (token.type == "NAME") {
792             if (this.mode == 'BUILDING_SYMBOL_TREE') {
793                 // Get the name of the function and declare it in the current scope.
794                 symbol = token.data;
795                 if (currentScope.getIdentifier(symbol) != false) {
796                     this.warn("The function " + symbol + " has already been declared in the same scope...", true);
797                 }
798                 currentScope.declareIdentifier(symbol,token);
799             }
800             token =  this.ts.nextTok();
801         }
802
803         //assert token.getType() == Token.LP;
804         if (this.mode == 'BUILDING_SYMBOL_TREE') {
805             fnScope = new Scope(this.braceNesting, currentScope, token.n, '');
806             
807             //println("STORING SCOPE" + this.ts.cursor);
808             
809             this.indexedScopes[this.ts.cursor] = fnScope;
810             
811         } else {
812             //qln("FETCHING SCOPE" + this.ts.cursor);
813             fnScope = this.indexedScopes[this.ts.cursor];
814           
815         }
816         
817         // Parse function arguments.
818         var argpos = 0;
819         while (this.ts.lookTok().data != ')') { //(token = consumeToken()).getType() != Token.RP) {
820             token = this.ts.nextTok();
821            // print ("FUNC ARGS: " + token.toString())
822             //assert token.getType() == Token.NAME ||
823             //        token.getType() == Token.COMMA;
824             if (token.type == 'NAME' && this.mode == 'BUILDING_SYMBOL_TREE') {
825                 symbol = token.data;
826                 identifier = fnScope.declareIdentifier(symbol,token);
827                 if (symbol == "$super" && argpos == 0) {
828                     // Exception for Prototype 1.6...
829                     identifier.preventMunging();
830                 }
831                 argpos++;
832             }
833         }
834
835         token = this.ts.nextTok();
836         print(token.toString());
837         // assert token.getType() == Token.LC;
838         this.braceNesting++;
839
840         token = this.ts.nextTok();
841         print(token.toString());
842         if (token.type == "STRN" && this.ts.lookTok(1).data == ';') {
843             /*
844             
845             NOT SUPPORTED YET!?!!?!
846             
847             // This is a hint. Hints are empty statements that look like
848             // "localvar1:nomunge, localvar2:nomunge"; They allow developers
849             // to prevent specific symbols from getting obfuscated (some heretic
850             // implementations, such as Prototype 1.6, require specific variable
851             // names, such as $super for example, in order to work appropriately.
852             // Note: right now, only "nomunge" is supported in the right hand side
853             // of a hint. However, in the future, the right hand side may contain
854             // other values.
855             consumeToken();
856             String hints = token.getValue();
857             // Remove the leading and trailing quotes...
858             hints = hints.substring(1, hints.length() - 1).trim();
859             StringTokenizer st1 = new StringTokenizer(hints, ",");
860             while (st1.hasMoreTokens()) {
861                 String hint = st1.nextToken();
862                 int idx = hint.indexOf(':');
863                 if (idx <= 0 || idx >= hint.length() - 1) {
864                     if (mode == BUILDING_SYMBOL_TREE) {
865                         // No need to report the error twice, hence the test...
866                         this.warn("Invalid hint syntax: " + hint, true);
867                     }
868                     break;
869                 }
870                 String variableName = hint.substring(0, idx).trim();
871                 String variableType = hint.substring(idx + 1).trim();
872                 if (mode == BUILDING_SYMBOL_TREE) {
873                     fnScope.addHint(variableName, variableType);
874                 } else if (mode == CHECKING_SYMBOL_TREE) {
875                     identifier = fnScope.getIdentifier(variableName);
876                     if (identifier != null) {
877                         if (variableType.equals("nomunge")) {
878                             identifier.preventMunging();
879                         } else {
880                             this.warn("Unsupported hint value: " + hint, true);
881                         }
882                     } else {
883                         this.warn("Hint refers to an unknown identifier: " + hint, true);
884                     }
885                 }
886             }
887             */
888         }
889
890         this.parseScope(fnScope);
891         // now pop it off the stack!!!
892        
893         this.braceNesting = b4braceNesting;
894         print("ENDFN -1: " + this.ts.lookTok(-1).toString());
895         print("ENDFN 0: " + this.ts.lookTok(0).toString());
896         print("ENDFN 1: " + this.ts.lookTok(1).toString());
897     },
898     
899     protectScopeFromObfuscation : function(scope) {
900             //assert scope != null;
901         
902         if (scope == this.globalScope) {
903             // The global scope does not get obfuscated,
904             // so we don't need to worry about it...
905             return;
906         }
907
908         // Find the highest local scope containing the specified scope.
909         while (scope && scope.parent != this.globalScope) {
910             scope = scope.parent;
911         }
912
913         //assert scope.getParentScope() == globalScope;
914         scope.preventMunging();
915     },
916     
917     getIdentifier: function(symbol, scope) {
918         var identifier;
919         while (scope != false) {
920             identifier = scope.getIdentifier(symbol);
921             //println("ScopeParser.getIdentgetUsedSymbols("+symbol+")=" + scope.getUsedSymbols().join(','));
922             if (identifier) {
923                 return identifier;
924             }
925             scope = scope.parent;
926         }
927         return false;
928     }
929 };