View Javadoc

1   /*
2    * Version: MPL 1.1
3    *
4    * The contents of this file are subject to the Mozilla Public License Version
5    * 1.1 (the "License"); you may not use this file except in compliance with
6    * the License. You may obtain a copy of the License at
7    * http://www.mozilla.org/MPL/
8    *
9    * Software distributed under the License is distributed on an "AS IS" basis,
10   * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11   * for the specific language governing rights and limitations under the
12   * License.
13   *
14   * The Original Code is Rhino code, released
15   * May 6, 1999.
16   *
17   * The Initial Developer of the Original Code is
18   * Netscape Communications Corporation.
19   * Portions created by the Initial Developer are Copyright (C) 1997-1999
20   * the Initial Developer. All Rights Reserved.
21   *
22   * Contributor(s):
23   *   Alex Russell
24   *   Richard Backhouse
25   */
26  
27   package org.dojotoolkit.shrinksafe;
28  
29  import java.io.IOException;
30  import java.io.Reader;
31  import java.util.HashMap;
32  import java.util.Iterator;
33  import java.util.Map;
34  import java.util.Stack;
35  import java.util.regex.Matcher;
36  import java.util.regex.Pattern;
37  
38  import org.mozilla.javascript.CompilerEnvirons;
39  import org.mozilla.javascript.Decompiler;
40  import org.mozilla.javascript.FunctionNode;
41  import org.mozilla.javascript.Interpreter;
42  import org.mozilla.javascript.Kit;
43  import org.mozilla.javascript.Parser;
44  import org.mozilla.javascript.ScriptOrFnNode;
45  import org.mozilla.javascript.ScriptRuntime;
46  import org.mozilla.javascript.Token;
47  import org.mozilla.javascript.UintMap;
48  
49  /**
50   * @author rbackhouse
51   *
52   */
53  public class Compressor {
54      private static final int FUNCTION_END = Token.LAST_TOKEN + 1;
55      
56      /**
57       * Compress the script
58       * <p>
59       * 
60       * @param encodedSource encoded source string
61       * @param flags Flags specifying format of decompilation output
62       * @param properties Decompilation properties
63       * @param parseTree Mapping for each function node and corresponding parameters & variables names
64       * @return compressed script
65       */
66      private static String compress(String encodedSource, 
67      		                       int flags, 
68      		                       UintMap properties, 
69      		                       ScriptOrFnNode parseTree, 
70      		                       boolean escapeUnicode,
71      		                       String stripConsole,
72      		                       TokenMapper tm,
73      		                       Map replacedTokensLookup){
74           int indent = properties.getInt(Decompiler.INITIAL_INDENT_PROP, 0);
75           if (indent < 0) throw new IllegalArgumentException();
76           int indentGap = properties.getInt(Decompiler.INDENT_GAP_PROP, 4);
77           if (indentGap < 0) throw new IllegalArgumentException();
78           int caseGap = properties.getInt(Decompiler.CASE_GAP_PROP, 2);
79           if (caseGap < 0) throw new IllegalArgumentException();
80  
81           String stripConsoleRegex = "assert|count|debug|dir|dirxml|group|groupEnd|info|profile|profileEnd|time|timeEnd|trace|log";
82           if (stripConsole == null) {
83          	 // may be null if unspecified on Main cmd line
84          	 stripConsoleRegex = null;
85           } else if (stripConsole.equals("normal")) {
86          	 // leave default
87           } else if (stripConsole.equals("warn")) {
88          	 stripConsoleRegex += "|warn";
89           } else if (stripConsole.equals("all")) {
90          	 stripConsoleRegex += "|warn|error";
91           } else {
92          	 throw new IllegalArgumentException("unrecognised value for stripConsole: " + stripConsole + "!");
93           }
94           
95           Pattern stripConsolePattern = null;
96           if (stripConsoleRegex != null) {
97          	 stripConsolePattern = Pattern.compile(stripConsoleRegex);
98           }
99  
100          StringBuffer result = new StringBuffer();
101          boolean justFunctionBody = (0 != (flags & Decompiler.ONLY_BODY_FLAG));
102          boolean toSource = (0 != (flags & Decompiler.TO_SOURCE_FLAG));
103          int braceNesting = 0;
104          boolean afterFirstEOL = false;
105          int i = 0;
106          int prevToken = 0;
107          boolean primeFunctionNesting = false;
108          boolean inArgsList = false;
109          boolean primeInArgsList = false;
110 
111          boolean discardingConsole = false; // control skipping "console.stuff()"
112          int     consoleParenCount = 0; // counter for parenthesis counting
113          StringBuffer discardMe = new StringBuffer(); // throwaway buffer
114          ReplacedTokens dummyTokens = new ReplacedTokens(new HashMap(), new int[]{}, replacedTokensLookup, null);
115          int lastMeaningfulToken = Token.SEMI;
116          int lastMeaningfulTokenBeforeConsole = Token.SEMI;
117 
118          int topFunctionType;
119          if (encodedSource.charAt(i) == Token.SCRIPT) {
120              ++i;
121              topFunctionType = -1;
122          } else {
123              topFunctionType = encodedSource.charAt(i + 1);
124          }
125          if (!toSource) {
126              // add an initial newline to exactly match js.
127              // result.append('\n');
128              for (int j = 0; j < indent; j++){
129                  // result.append(' ');
130                  result.append("");
131              }
132          } else {
133              if (topFunctionType == FunctionNode.FUNCTION_EXPRESSION) {
134                  result.append('(');
135              }
136          }
137          
138          Stack positionStack = new Stack();
139          Stack functionPositionStack = new Stack();
140          
141          int length = encodedSource.length();
142          int lineCount = 1;
143          
144          while (i < length) {
145            if(i>0){
146                prevToken = encodedSource.charAt(i-1);
147            }
148             if (discardingConsole) {
149                 // while we are skipping a console command, discard tokens
150                 int thisToken = encodedSource.charAt(i);
151                 /* Logic for controlling state of discardingConsole */
152                 switch (thisToken) {
153                 case Token.LP:
154                     consoleParenCount++;
155                     break;
156                 case Token.RP:
157                     consoleParenCount--;
158                     if (consoleParenCount == 0) {
159                         // paren count fell to zero, must be end of console call
160                         discardingConsole = false;
161                         
162                         if (i < (length - 1)) {
163                         	int nextToken = getNext(encodedSource, length, i);
164 
165                         	if ((lastMeaningfulTokenBeforeConsole != Token.SEMI &&
166                         			lastMeaningfulTokenBeforeConsole != Token.LC &&
167                         			lastMeaningfulTokenBeforeConsole != Token.RC) ||
168                         			nextToken != Token.SEMI) {
169                         		// Either the previous or the following token
170                         		// may use our return value, insert undefined
171                         		// e.g. true ? console.log("bizarre") : (bar = true);
172                         		result.append("undefined");
173                         	} else {
174                             	if (Token.SEMI == nextToken) {
175                             		// munch following semicolon
176                             		i++;
177                             	}
178                         	}
179 						}
180 						if ((i < (length - 1))
181 								&& (Token.EOL == getNext(encodedSource, length, i))) {
182 							// as a nicety, munch following linefeed
183 							i++;
184 						}
185                     }
186                     break;
187                 }
188                 /*
189                  * advance i - borrow code from later switch statements (could
190                  * mingle this whole discardingConsole block in with rest of
191                  * function but it would be _ugly_) Use discardMe in place of
192                  * result, so we don't use the processed source Specific case
193                  * blocks for all source elements > 1 char long
194                  */
195                 switch (thisToken) {
196                 case Token.NAME:
197                 case Token.REGEXP:
198                     int jumpPos = getSourceStringEnd(encodedSource, i + 1,
199                             escapeUnicode);
200                     if (Token.OBJECTLIT == encodedSource.charAt(jumpPos)) {
201                         i = printSourceString(encodedSource, i + 1, false,
202                                 discardMe, escapeUnicode);
203                     } else {
204                         i = tm.sourceCompress(encodedSource, i + 1, false,
205                                 discardMe, prevToken, inArgsList, braceNesting,
206                                 dummyTokens);
207                     }
208                     break;
209                 case Token.STRING:
210                     i = printSourceString(encodedSource, i + 1, true,
211                             discardMe, escapeUnicode);
212                     break;
213                 case Token.NUMBER:
214                     i = printSourceNumber(encodedSource, i + 1, discardMe);
215                     break;
216                 default:
217                     // all plain tokens (no data to skip)
218                     i++;
219                 }
220                 // while discarding console, avoid the normal processing
221                 continue;
222             }
223 
224            // System.out.println(Token.name(getNext(source, length, i)));
225            int thisToken = encodedSource.charAt(i);
226 
227            switch(thisToken) {
228              case Token.NAME:
229              case Token.REGEXP:  // re-wrapped in '/'s in parser...
230                  int jumpPos = getSourceStringEnd(encodedSource, i+1, escapeUnicode);
231                  if (stripConsolePattern != null && thisToken == Token.NAME) {
232                 	 // Check to see if this is a console.something() call that we
233                 	 //  care about, if so switch on discardingConsole
234                      int nextTokenAt = tm.sourceCompress(encodedSource, i + 1, false, discardMe, prevToken, 
235                              inArgsList, braceNesting, dummyTokens);
236                 	 if (encodedSource.substring(i+2, i+2+encodedSource.charAt(i+1)).equals("console") &&
237                          (encodedSource.charAt(nextTokenAt) == Token.DOT)) {
238                 		 // Find the name of the console method and check it
239                     	 int afterFnName = printSourceString(encodedSource, nextTokenAt+2, false, discardMe, escapeUnicode);
240                     	 Matcher m = stripConsolePattern.matcher(encodedSource.substring(nextTokenAt + 3, afterFnName));
241                     	 if (m.matches()) {
242                     		 // Must be an open parenthesis e.g. "console.log("
243                     		 if (encodedSource.charAt(afterFnName) == Token.LP) {
244 		                		 discardingConsole = true;
245 		                		 consoleParenCount = 0;
246 		                		 lastMeaningfulTokenBeforeConsole = lastMeaningfulToken;
247 		                		 continue;
248                     		 }
249                     	 }
250                      }
251                  }
252                  if(Token.OBJECTLIT == encodedSource.charAt(jumpPos)){
253                      i = printSourceString(encodedSource, i + 1, false, result, escapeUnicode);
254                  }else{
255                 	 ReplacedTokens replacedTokens = null;
256                 	 if (positionStack.size() > 0) {
257                 		 Integer pos = (Integer)positionStack.peek();
258                          replacedTokens = (ReplacedTokens)replacedTokensLookup.get(pos);
259                 	 }
260                 	 else {
261                 		 replacedTokens = new ReplacedTokens(new HashMap(), new int[]{}, replacedTokensLookup, null);
262                 	 }
263 
264                      i = tm.sourceCompress(	encodedSource, i + 1, false, result, prevToken, 
265                                              inArgsList, braceNesting, replacedTokens);
266                  }
267                  continue;
268              case Token.STRING:
269 
270 // NOTE: this is the disabled "string munging" code provided in bugs.dojotoolkit.org/ticket/8828
271 // simply uncomment this block, and run the build.sh script located in the root shrinksafe folder.
272 // there is a far-egde-case this is deemed unsafe in, so is entirely disabled for sanity of devs.
273 //
274 //                 StringBuffer buf = new StringBuffer();
275 //                 i--;
276 //                 do {
277 //                    i++;
278 //                    i = printSourceString(encodedSource, i + 1, false, buf, escapeUnicode);
279 //                 } while(Token.ADD == encodedSource.charAt(i) &&
280 //                   Token.STRING == getNext(encodedSource, length, i));
281 //                 result.append('"');
282 //                 result.append(escapeString(buf.toString(), escapeUnicode));
283 //                 result.append('"');
284 //
285 // now comment out this line to complete the patch:
286                  i = printSourceString(encodedSource, i + 1, true, result, escapeUnicode); 
287 
288                  continue;
289              case Token.NUMBER:
290                  i = printSourceNumber(encodedSource, i + 1, result);
291                  continue;
292              case Token.TRUE:
293                  result.append("true");
294                  break;
295              case Token.FALSE:
296                  result.append("false");
297                  break;
298              case Token.NULL:
299                  result.append("null");
300                  break;
301              case Token.THIS:
302                  result.append("this");
303                  break;
304              case Token.FUNCTION: {
305                  ++i; // skip function type
306                  tm.incrementFunctionNumber();
307                  primeInArgsList = true;
308                  primeFunctionNesting = true;
309                  result.append("function");
310                  if (Token.LP != getNext(encodedSource, length, i)) {
311                      result.append(' ');
312                  }
313                  Integer functionPos = new Integer(i-1);
314                  functionPositionStack.push(functionPos);
315                  DebugData debugData = tm.getDebugData(functionPos);
316                  debugData.compressedStart = lineCount;
317                  break;
318              }
319              case FUNCTION_END: {
320             	 Integer functionPos = (Integer)functionPositionStack.pop();
321                  DebugData debugData = tm.getDebugData(functionPos);
322                  debugData.compressedEnd = lineCount;
323                  break;
324              }
325              case Token.COMMA:
326                  result.append(",");
327                  break;
328              case Token.LC:
329                  ++braceNesting;
330                  if (Token.EOL == getNext(encodedSource, length, i)){
331                      indent += indentGap;
332                  }
333                  result.append('{');
334                  // // result.append('\n');
335                  break;
336              case Token.RC: {
337                  if (tm.leaveNestingLevel(braceNesting)) {
338                      positionStack.pop();
339                  }
340                  --braceNesting;
341                  /* don't print the closing RC if it closes the
342                   * toplevel function and we're called from
343                   * decompileFunctionBody.
344                   */
345                  if(justFunctionBody && braceNesting == 0){
346                      break;
347                  }
348                  // // result.append('\n');
349                  result.append('}');
350                  // // result.append(' ');
351                  switch (getNext(encodedSource, length, i)) {
352                      case Token.EOL:
353                      case FUNCTION_END:
354                         if (
355                             (getNext(encodedSource, length, i+1) != Token.SEMI) &&
356                             (getNext(encodedSource, length, i+1) != Token.LP) &&
357                             (getNext(encodedSource, length, i+1) != Token.RP) &&
358                             (getNext(encodedSource, length, i+1) != Token.RB) &&
359                             (getNext(encodedSource, length, i+1) != Token.RC) &&
360                             (getNext(encodedSource, length, i+1) != Token.COMMA) &&
361                             (getNext(encodedSource, length, i+1) != Token.COLON) &&
362                             (getNext(encodedSource, length, i+1) != Token.DOT) &&
363                             (getNext(encodedSource, length, i) == FUNCTION_END )
364                          ){
365 						    result.append(';');
366                          }
367                          indent -= indentGap;
368                          break;
369                      case Token.WHILE:
370                      case Token.ELSE:
371                          indent -= indentGap;
372                          // result.append(' ');
373                          result.append("");
374                          break;
375                  }
376                  break;
377              }
378              case Token.LP:
379                  if(primeInArgsList){
380                      inArgsList = true;
381                      primeInArgsList = false;
382                  }
383                  if(primeFunctionNesting){
384                      positionStack.push(new Integer(i));
385                      tm.enterNestingLevel(braceNesting);
386                      primeFunctionNesting = false;
387                  }
388                  result.append('(');
389                  break;
390              case Token.RP:
391 			    if(inArgsList){
392                     inArgsList = false;
393 				}
394                  result.append(')');
395   			/*
396                  if (Token.LC == getNext(source, length, i)){
397                      result.append(' ');
398                  }
399   			*/
400                  break;
401              case Token.LB:
402                  result.append('[');
403                  break;
404              case Token.RB:
405                  result.append(']');
406                  break;
407              case Token.EOL: {
408                  if (toSource) break;
409                  boolean newLine = true;
410                  if (!afterFirstEOL) {
411                      afterFirstEOL = true;
412                      if (justFunctionBody) {
413                          /* throw away just added 'function name(...) {'
414                           * and restore the original indent
415                           */
416                          result.setLength(0);
417                          indent -= indentGap;
418                          newLine = false;
419                      }
420                  }
421                  if (newLine) {
422                      result.append('\n');
423                      lineCount++;
424                  }
425                  /* add indent if any tokens remain,
426                   * less setback if next token is
427                   * a label, case or default.
428                   */
429                  if (i + 1 < length) {
430                      int less = 0;
431                      int nextToken = encodedSource.charAt(i + 1);
432                      if (nextToken == Token.CASE
433                          || nextToken == Token.DEFAULT)
434                      {
435                          less = indentGap - caseGap;
436                      } else if (nextToken == Token.RC) {
437                          less = indentGap;
438                      }
439                      /* elaborate check against label... skip past a
440                       * following inlined NAME and look for a COLON.
441                       */
442                      else if (nextToken == Token.NAME) {
443                          int afterName = getSourceStringEnd(encodedSource, i + 2, escapeUnicode);
444                          if (encodedSource.charAt(afterName) == Token.COLON)
445                              less = indentGap;
446                      }
447                      for (; less < indent; less++){
448                          // result.append(' ');
449                          result.append("");
450                      }
451                  }
452                  break;
453              }
454              case Token.DOT:
455                  result.append('.');
456                  break;
457              case Token.NEW:
458                  result.append("new ");
459                  break;
460              case Token.DELPROP:
461                  result.append("delete ");
462                  break;
463              case Token.IF:
464                  result.append("if");
465                  break;
466              case Token.ELSE:
467                  result.append("else");
468                  break;
469              case Token.FOR:
470                  result.append("for");
471                  break;
472              case Token.IN:
473                  result.append(" in ");
474                  break;
475              case Token.WITH:
476                  result.append("with");
477                  break;
478              case Token.WHILE:
479                  result.append("while");
480                  break;
481              case Token.DO:
482                  result.append("do");
483                  break;
484              case Token.TRY:
485                  result.append("try");
486                  break;
487              case Token.CATCH:
488                  result.append("catch");
489                  break;
490              case Token.FINALLY:
491                  result.append("finally");
492                  break;
493              case Token.THROW:
494                  result.append("throw ");
495                  break;
496              case Token.SWITCH:
497                  result.append("switch");
498                  break;
499              case Token.BREAK:
500                  result.append("break");
501                  if(Token.NAME == getNext(encodedSource, length, i)){
502                      result.append(' ');
503   			}
504                  break;
505              case Token.CONTINUE:
506                  result.append("continue");
507                  if(Token.NAME == getNext(encodedSource, length, i)){
508                      result.append(' ');
509   			}
510                  break;
511              case Token.CASE:
512                  result.append("case ");
513                  break;
514              case Token.DEFAULT:
515                  result.append("default");
516                  break;
517              case Token.RETURN:
518                  result.append("return");
519                  if(Token.SEMI != getNext(encodedSource, length, i)){
520                      result.append(' ');
521                  }
522                  break;
523              case Token.VAR:
524                  result.append("var ");
525                  break;
526              case Token.SEMI:
527                  result.append(';');
528                  // result.append('\n');
529   			/*
530                  if (Token.EOL != getNext(source, length, i)) {
531                      // separators in FOR
532                      result.append(' ');
533                  }
534   			*/
535                  break;
536              case Token.ASSIGN:
537                  result.append("=");
538                  break;
539              case Token.ASSIGN_ADD:
540                  result.append("+=");
541                  break;
542              case Token.ASSIGN_SUB:
543                  result.append("-=");
544                  break;
545              case Token.ASSIGN_MUL:
546                  result.append("*=");
547                  break;
548              case Token.ASSIGN_DIV:
549                  result.append("/=");
550                  break;
551              case Token.ASSIGN_MOD:
552                  result.append("%=");
553                  break;
554              case Token.ASSIGN_BITOR:
555                  result.append("|=");
556                  break;
557              case Token.ASSIGN_BITXOR:
558                  result.append("^=");
559                  break;
560              case Token.ASSIGN_BITAND:
561                  result.append("&=");
562                  break;
563              case Token.ASSIGN_LSH:
564                  result.append("<<=");
565                  break;
566              case Token.ASSIGN_RSH:
567                  result.append(">>=");
568                  break;
569              case Token.ASSIGN_URSH:
570                  result.append(">>>=");
571                  break;
572              case Token.HOOK:
573                  result.append("?");
574                  break;
575              case Token.OBJECTLIT:
576                  // pun OBJECTLIT to mean colon in objlit property
577                  // initialization.
578                  // This needs to be distinct from COLON in the general case
579                  // to distinguish from the colon in a ternary... which needs
580                  // different spacing.
581                  result.append(':');
582                  break;
583              case Token.COLON:
584                  if (Token.EOL == getNext(encodedSource, length, i))
585                      // it's the end of a label
586                      result.append(':');
587                  else
588                      // it's the middle part of a ternary
589                      result.append(":");
590                  break;
591              case Token.OR:
592                  result.append("||");
593                  break;
594              case Token.AND:
595                  result.append("&&");
596                  break;
597              case Token.BITOR:
598                  result.append("|");
599                  break;
600              case Token.BITXOR:
601                  result.append("^");
602                  break;
603              case Token.BITAND:
604                  result.append("&");
605                  break;
606              case Token.SHEQ:
607                  result.append("===");
608                  break;
609              case Token.SHNE:
610                  result.append("!==");
611                  break;
612              case Token.EQ:
613                  result.append("==");
614                  break;
615              case Token.NE:
616                  result.append("!=");
617                  break;
618              case Token.LE:
619                  result.append("<=");
620                  break;
621              case Token.LT:
622                  result.append("<");
623                  break;
624              case Token.GE:
625                  result.append(">=");
626                  break;
627              case Token.GT:
628                  result.append(">");
629                  break;
630              case Token.INSTANCEOF:
631   			// FIXME: does this really need leading space?
632                  result.append(" instanceof ");
633                  break;
634              case Token.LSH:
635                  result.append("<<");
636                  break;
637              case Token.RSH:
638                  result.append(">>");
639                  break;
640              case Token.URSH:
641                  result.append(">>>");
642                  break;
643              case Token.TYPEOF:
644                  result.append("typeof ");
645                  break;
646              case Token.VOID:
647                  result.append("void ");
648                  break;
649              case Token.NOT:
650                  result.append('!');
651                  break;
652              case Token.BITNOT:
653                  result.append('~');
654                  break;
655              case Token.POS:
656                  result.append('+');
657                  break;
658              case Token.NEG:
659                  result.append('-');
660                  break;
661              case Token.INC:
662                  if(Token.ADD == prevToken){
663                     result.append(' ');
664                  }
665                  result.append("++");
666                  if(Token.ADD == getNext(encodedSource, length, i)){
667                      result.append(' ');
668                  }
669                  break;
670              case Token.DEC:
671                  if(Token.SUB == prevToken){
672                      result.append(' ');
673                  }
674                  result.append("--");
675                  if(Token.SUB == getNext(encodedSource, length, i)){
676                      result.append(' ');
677                  }
678                  break;
679              case Token.ADD:
680                  result.append("+");
681                  int nextToken = encodedSource.charAt(i + 1);
682                  if (nextToken == Token.POS) {
683                      result.append(' ');
684                  }
685                  break;
686              case Token.SUB:
687                  result.append("-");
688                  nextToken = encodedSource.charAt(i + 1);
689                  if (nextToken == Token.NEG) {
690                      result.append(' ');
691                  }
692                  break;
693              case Token.MUL:
694                  result.append("*");
695                  break;
696              case Token.DIV:
697                  result.append("/");
698                  break;
699              case Token.MOD:
700                  result.append("%");
701                  break;
702              case Token.COLONCOLON:
703                  result.append("::");
704                  break;
705              case Token.DOTDOT:
706                  result.append("..");
707                  break;
708              case Token.XMLATTR:
709                  result.append('@');
710                  break;
711             case Token.DEBUGGER:
712                 System.out.println("WARNING: Found a `debugger;` statement in code being compressed");
713                 result.append("debugger");
714                 break;
715              default:
716                  // If we don't know how to decompile it, raise an exception.
717                  throw new RuntimeException();
718              }
719              if (thisToken != Token.EOL) {
720                  lastMeaningfulToken = thisToken;
721              }
722              ++i;
723          }
724          if (!toSource) {
725              // add that trailing newline if it's an outermost function.
726              // if (!justFunctionBody){
727              //    result.append('\n');
728   		// }
729          } else {
730              if (topFunctionType == FunctionNode.FUNCTION_EXPRESSION) {
731                  result.append(')');
732              }
733          }
734          return result.toString();
735     }
736 
737     /**
738      * Collect the replaced tokens and store them in a lookup table for the next
739      * source pass. 
740      * 
741      * @param encodedSource encoded source string
742      * @param escapeUnicode escape chars with unicode.
743      * @param tm token mapper object.
744      * @return Map containing replaced tokens lookup information
745      */
746     private static Map collectReplacedTokens(String encodedSource, boolean escapeUnicode, TokenMapper tm) {
747     	int length = encodedSource.length();
748     	
749 		int i = 0;
750 		int prevToken = 0;
751 		int braceNesting = 0;
752 		
753 		boolean inArgsList = false;
754         boolean primeFunctionNesting = false;
755         boolean primeInArgsList = false;
756 		
757         if (encodedSource.charAt(i) == Token.SCRIPT) {
758             ++i;
759         }
760 
761         Stack positionStack = new Stack();
762         Stack functionPositionStack = new Stack();
763         Map tokenLookup = new HashMap();
764         
765 		while (i < length) {
766 			if (i > 0) {
767 				prevToken = encodedSource.charAt(i - 1);
768 			}
769 			switch (encodedSource.charAt(i)) {
770 				case Token.NAME:
771 				case Token.REGEXP: {
772 					int jumpPos = getSourceStringEnd(encodedSource, i + 1, escapeUnicode);
773 					if (Token.OBJECTLIT == encodedSource.charAt(jumpPos)) {
774 						i = printSourceString(encodedSource, i + 1, false, null, escapeUnicode);
775 					} else {
776 						i = tm.sourceCompress(encodedSource, i + 1, false, null, prevToken, inArgsList, braceNesting, null);
777 					}
778 					continue;
779 				}
780 				case Token.STRING: {
781 					i = printSourceString(encodedSource, i + 1, true, null, escapeUnicode);
782 					continue;
783 				}
784 				case Token.NUMBER: {
785 					i = printSourceNumber(encodedSource, i + 1, null);
786 					continue;
787 				}
788 				case Token.FUNCTION: {
789 					++i; // skip function type
790 					tm.incrementFunctionNumber();
791 					primeInArgsList = true;
792 					primeFunctionNesting = true;
793 					functionPositionStack.push(new Integer(i-1));
794 					break;
795 				}
796 				case Token.LC: {
797 					++braceNesting;
798 					break;
799 				}
800 				case Token.RC: {
801 					Map m = tm.getCurrentTokens();
802 					if (tm.leaveNestingLevel(braceNesting)) {
803 						Integer pos = (Integer)positionStack.pop();
804 						Integer functionPos = (Integer)functionPositionStack.pop();
805 						int[] parents = new int[positionStack.size()];
806 						int idx = 0;
807 						for (Iterator itr = positionStack.iterator(); itr.hasNext();) {
808 							parents[idx++] = ((Integer)itr.next()).intValue();
809 						}
810 						DebugData debugData = tm.getDebugData(functionPos);
811 						ReplacedTokens replacedTokens = new ReplacedTokens(m, parents, tokenLookup, debugData); 
812 						tokenLookup.put(pos, replacedTokens);
813 					}
814 					--braceNesting;
815 					break;
816 				}
817 				case Token.LP: {
818 					if (primeInArgsList) {
819 						inArgsList = true;
820 						primeInArgsList = false;
821 					}
822 					if (primeFunctionNesting) {
823 						positionStack.push(new Integer(i));
824 						tm.enterNestingLevel(braceNesting);
825 						primeFunctionNesting = false;
826 					}
827 					break;
828 				}
829 				case Token.RP: {
830 					if (inArgsList) {
831 						inArgsList = false;
832 					}
833 					break;
834 				}
835 			}
836 			++i;
837 		}
838 		return tokenLookup;
839 	}
840 
841     private static int getNext(String source, int length, int i) {
842         return (i + 1 < length) ? source.charAt(i + 1) : Token.EOF;
843     }
844 
845     private static int getSourceStringEnd(String source, int offset, boolean escapeUnicode) {
846         return printSourceString(source, offset, false, null, escapeUnicode);
847     }
848 
849     private static int printSourceString(String source, int offset,
850                                          boolean asQuotedString,
851                                          StringBuffer sb,
852                                          boolean escapeUnicode) {
853         int length = source.charAt(offset);
854         ++offset;
855         if ((0x8000 & length) != 0) {
856             length = ((0x7FFF & length) << 16) | source.charAt(offset);
857             ++offset;
858         }
859         if (sb != null) {
860             String str = source.substring(offset, offset + length);
861             if (!asQuotedString) {
862                 sb.append(str);
863             } else {
864                 sb.append('"');
865                 sb.append(escapeString(str, escapeUnicode));
866                 sb.append('"');
867             }
868         }
869         return offset + length;
870     }
871     
872     private static int printSourceNumber(String source, int offset,	StringBuffer sb) {
873 		double number = 0.0;
874 		char type = source.charAt(offset);
875 		++offset;
876 		if (type == 'S') {
877 			if (sb != null) {
878 				int ival = source.charAt(offset);
879 				number = ival;
880 			}
881 			++offset;
882 		} else if (type == 'J' || type == 'D') {
883 			if (sb != null) {
884 				long lbits;
885 				lbits = (long) source.charAt(offset) << 48;
886 				lbits |= (long) source.charAt(offset + 1) << 32;
887 				lbits |= (long) source.charAt(offset + 2) << 16;
888 				lbits |= source.charAt(offset + 3);
889 				if (type == 'J') {
890 					number = lbits;
891 				} else {
892 					number = Double.longBitsToDouble(lbits);
893 				}
894 			}
895 			offset += 4;
896 		} else {
897 			// Bad source
898 			throw new RuntimeException();
899 		}
900 		if (sb != null) {
901 			sb.append(ScriptRuntime.numberToString(number, 10));
902 		}
903 		return offset;
904 	}
905     
906     private static String escapeString(String s, boolean escapeUnicode) {
907         return escapeString(s, '"', escapeUnicode);
908     }
909     
910     private static String escapeString(String s, char escapeQuote, boolean escapeUnicode) {
911         if (!(escapeQuote == '"' || escapeQuote == '\'')) Kit.codeBug();
912         StringBuffer sb = null;
913 
914         for(int i = 0, L = s.length(); i != L; ++i) {
915             int c = s.charAt(i);
916 
917             if (' ' <= c && c <= '~' && c != escapeQuote && c != '\\') {
918                 // an ordinary print character (like C isprint()) and not "
919                 // or \ .
920                 if (sb != null) {
921                     sb.append((char)c);
922                 }
923                 continue;
924             }
925             if (sb == null) {
926                 sb = new StringBuffer(L + 3);
927                 sb.append(s);
928                 sb.setLength(i);
929             }
930 
931             int escape = -1;
932             switch (c) {
933                 case '\b':  escape = 'b';  break;
934                 case '\f':  escape = 'f';  break;
935                 case '\n':  escape = 'n';  break;
936                 case '\r':  escape = 'r';  break;
937                 case '\t':  escape = 't';  break;
938                 case 0xb:   escape = 'v';  break; // Java lacks \v.
939                 case ' ':   escape = ' ';  break;
940                 case '\\':  escape = '\\'; break;
941             }
942             if (escape >= 0) {
943                 // an \escaped sort of character
944                 sb.append('\\');
945                 sb.append((char)escape);
946             } else if (c == escapeQuote) {
947                 sb.append('\\');
948                 sb.append(escapeQuote);
949             } else {
950             	if (escapeUnicode || c == 0) { // always escape the null character (#5027)
951 	                int hexSize;
952 	                if (c < 256) {
953 	                    // 2-digit hex
954 	                    sb.append("\\x");
955 	                    hexSize = 2;
956 	                } else {
957 	                    // Unicode.
958 	                    sb.append("\\u");
959 	                    hexSize = 4;
960 	                }
961 	                // append hexadecimal form of c left-padded with 0
962 	                for (int shift = (hexSize - 1) * 4; shift >= 0; shift -= 4) {
963 	                    int digit = 0xf & (c >> shift);
964 	                    int hc = (digit < 10) ? '0' + digit : 'a' - 10 + digit;
965 	                    sb.append((char)hc);
966 	                }
967             	}
968             	else {
969             		sb.append((char)c);
970             	}
971             }
972         }
973         return (sb == null) ? s : sb.toString();
974     }
975 
976     public static final String compressScript(Reader source)
977         throws IOException
978     {
979     	return compressScript(source, 0, 1, false, null);
980     }
981 
982     public static final String compressScript(Reader source, String stripConsole)
983         throws IOException
984     {
985     	return compressScript(source, 0, 1, false, stripConsole);
986     }
987 
988     public static final String compressScript(Reader source, int indent, int lineno, String stripConsole)
989         throws IOException
990     {
991     	return compressScript(source, indent, lineno, false, stripConsole);
992     }
993     
994     public static final String compressScript(Reader source, int indent, int lineno, boolean escapeUnicode, String stripConsole)
995         throws IOException
996     {
997     	return compressScript(source, indent, lineno, escapeUnicode, stripConsole, null);
998     }
999         
1000     public static final String compressScript(Reader source, int indent, int lineno, boolean escapeUnicode, String stripConsole, StringBuffer debugData)
1001         throws IOException
1002     {
1003         CompilerEnvirons compilerEnv = new CompilerEnvirons();
1004 
1005         Parser parser = new Parser(compilerEnv, compilerEnv.getErrorReporter());
1006         
1007         ScriptOrFnNode tree = parser.parse(source, null, lineno);
1008         String encodedSource = parser.getEncodedSource();
1009    	 	if (encodedSource.length() == 0) { return ""; }
1010    	 	
1011         Interpreter compiler = new Interpreter();
1012         compiler.compile(compilerEnv, tree, encodedSource, false);
1013         UintMap properties = new UintMap(1);
1014         properties.put(Decompiler.INITIAL_INDENT_PROP, indent);
1015         
1016         TokenMapper tm = new TokenMapper(tree);
1017         Map replacedTokensLookup = collectReplacedTokens(encodedSource, escapeUnicode, tm);
1018         tm.reset();
1019         
1020         String compressedSource = compress(encodedSource, 0, properties, tree, escapeUnicode, stripConsole, tm, replacedTokensLookup);
1021         if (debugData != null) {
1022         	debugData.append("[\n");
1023         	int count = 1;
1024 	        for (Iterator itr = replacedTokensLookup.keySet().iterator(); itr.hasNext();) {
1025 	        	Integer pos = (Integer)itr.next();
1026 	        	ReplacedTokens replacedTokens = (ReplacedTokens)replacedTokensLookup.get(pos);
1027 	        	debugData.append(replacedTokens.toJson());
1028 	        	if (count++ < replacedTokensLookup.size()) {
1029 	        		debugData.append(',');
1030 	        	}
1031 	        	debugData.append("\n");
1032 	        }
1033         	debugData.append("]");
1034         }
1035         return compressedSource;
1036     }
1037 }