### Eclipse Workspace Patch 1.0 #P rhino Index: src/org/mozilla/javascript/BaseFunction.java =================================================================== RCS file: /cvsroot/mozilla/js/rhino/src/org/mozilla/javascript/BaseFunction.java,v retrieving revision 1.57 diff -u -r1.57 BaseFunction.java --- src/org/mozilla/javascript/BaseFunction.java 30 Aug 2005 10:05:42 -0000 1.57 +++ src/org/mozilla/javascript/BaseFunction.java 2 Apr 2007 22:06:50 -0000 @@ -373,6 +373,28 @@ return sb.toString(); } + String compress(int indent, int flags) + { + StringBuffer sb = new StringBuffer(); + String FuncName = null; + boolean justbody = (0 != (flags & Decompiler.ONLY_BODY_FLAG)); + if (!justbody) { + sb.append("function"); + FuncName = getFunctionName(); + if(FuncName.length()>0){ + sb.append(" "+FuncName); + } + sb.append("(){"); + } + sb.append("[native code, arity="); + sb.append(getArity()); + sb.append("]"); + if (!justbody) { + sb.append("}"); + } + return sb.toString(); + } + public int getArity() { return 0; } public int getLength() { return 0; } Index: src/org/mozilla/javascript/NativeFunction.java =================================================================== RCS file: /cvsroot/mozilla/js/rhino/src/org/mozilla/javascript/NativeFunction.java,v retrieving revision 1.62 diff -u -r1.62 NativeFunction.java --- src/org/mozilla/javascript/NativeFunction.java 17 Jan 2005 13:06:33 -0000 1.62 +++ src/org/mozilla/javascript/NativeFunction.java 2 Apr 2007 22:06:50 -0000 @@ -70,6 +70,26 @@ } } + /** + * Compress the script. + *
+ * + * @param parseTree Mapping for each function node and corresponding parameters & variables names + * @param indent How much to indent the decompiled result + * @param flags Flags specifying format of decompilation output + * @return compressed script + */ + final String compress(ScriptOrFnNode parseTree, int indent, int flags) + { + String encodedSource = getEncodedSource(); + if (encodedSource == null) { + return super.compress(indent, flags); + } else { + UintMap properties = new UintMap(1); + properties.put(Decompiler.INITIAL_INDENT_PROP, indent); + return Decompiler.compress(encodedSource, flags, properties, parseTree); + } + } public int getLength() { int paramCount = getParamCount(); Index: src/org/mozilla/javascript/TokenStream.java =================================================================== RCS file: /cvsroot/mozilla/js/rhino/src/org/mozilla/javascript/TokenStream.java,v retrieving revision 1.63 diff -u -r1.63 TokenStream.java --- src/org/mozilla/javascript/TokenStream.java 31 Jul 2005 13:48:46 -0000 1.63 +++ src/org/mozilla/javascript/TokenStream.java 2 Apr 2007 22:06:50 -0000 @@ -64,9 +64,12 @@ private final static int EOF_CHAR = -1; + public StringBuffer lastComment; + TokenStream(Parser parser, Reader sourceReader, String sourceString, int lineno) { + this.lastComment = new StringBuffer(); this.parser = parser; this.lineno = lineno; if (sourceReader != null) { @@ -736,6 +739,8 @@ case '/': // is it a // comment? + // FIXME: RAR: comment, need to set config to optionally keep + // instead of skipping! if (matchChar('/')) { skipLine(); continue retry; Index: src/org/mozilla/javascript/Context.java =================================================================== RCS file: /cvsroot/mozilla/js/rhino/src/org/mozilla/javascript/Context.java,v retrieving revision 1.244 diff -u -r1.244 Context.java --- src/org/mozilla/javascript/Context.java 4 Nov 2005 13:37:45 -0000 1.244 +++ src/org/mozilla/javascript/Context.java 2 Apr 2007 22:06:50 -0000 @@ -1178,6 +1178,33 @@ } } + public final String decompileReader(Scriptable scope, Reader in, + String sourceName, int lineno, + Object securityDomain) + throws IOException + { + Script script = compileReader(scope, in, sourceName, lineno, + securityDomain); + if (script != null) { + // System.err.println(script); + return decompileScript(script, 0); + } else { + return null; + } + } + + public final String compressReader(Scriptable scope, Script script, String source, + String sourceName, int lineno, Object securityDomain){ + + if (script != null) { + // System.err.println(script); + return compressScript(script, 0, source,lineno); + } else { + return null; + } + } + + /** * Check whether a string is ready to be compiled. *
@@ -1361,6 +1388,33 @@ } /** + * Compress the script. + *
+ * Compressed script is returned. + * + * @param script the script object + * @param indent the number of spaces to indent the result + * @return a string representing the script source + */ + public final String compressScript(Script script, int indent, String source,int lineno) + { + //Make sure to clear out the TokenMapper state before running. + //This allows for more than one script to be compressed. + //However, this is not a very clean way to do the reset. + TokenMapper.reset(); + Decompiler.tm = new TokenMapper(); + + NativeFunction scriptImpl = (NativeFunction) script; + + CompilerEnvirons compilerEnv = new CompilerEnvirons(); + + Parser parser = new Parser(compilerEnv, compilerEnv.getErrorReporter()); + + ScriptOrFnNode tree = parser.parse(source, null, lineno); + + return scriptImpl.compress( tree,indent, 0); + } + /** * Decompile a JavaScript Function. *
* Decompiles a previously compiled JavaScript function object to @@ -2240,7 +2294,6 @@ sourceReader = null; } } - Parser p = new Parser(compilerEnv, compilationErrorReporter); if (returnFunction) { p.calledByCompileFunction = true; @@ -2251,6 +2304,7 @@ } else { tree = p.parse(sourceReader, sourceName, lineno); } + if (returnFunction) { if (!(tree.getFunctionCount() == 1 && tree.getFirstChild() != null Index: src/org/mozilla/javascript/Decompiler.java =================================================================== RCS file: /cvsroot/mozilla/js/rhino/src/org/mozilla/javascript/Decompiler.java,v retrieving revision 1.19 diff -u -r1.19 Decompiler.java --- src/org/mozilla/javascript/Decompiler.java 28 Aug 2005 23:25:22 -0000 1.19 +++ src/org/mozilla/javascript/Decompiler.java 2 Apr 2007 22:06:50 -0000 @@ -37,6 +37,11 @@ package org.mozilla.javascript; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + /** * The following class save decompilation information about the source. * Source information is returned from the parser as a String @@ -70,6 +75,272 @@ * the final constant pool entry from information available at parse * time. */ + +class TokenMapper { + private ArrayList functionBracePositions = new ArrayList(); + + /** + * Map of all replaced tokens + */ + private ArrayList replacedTokens = new ArrayList(); + + /** + * Collection of Function nodes + */ + private static ObjArray funcObjects = new ObjArray(); + + /** + * Map of each Function node and all the variables in its current function + * scope, other variables found while traversing the prototype chain and + * variables found in the top-level scope. + */ + private static ArrayList functionVarMappings = new ArrayList(); + + public int functionNum = 0; + + private int parentScope = 0; + + private int lastTokenCount = 0; + + /** + * Reset the static members for the TokenMapper. + */ + public static void reset() { + funcObjects = new ObjArray(); + functionVarMappings = new ArrayList(); + } + + /** + * Generate new compressed tokens + *
+ * + * @param token + * value of the string token + * @param hasNewMapping + * boolean value indicating a new variable binding + * @return compressed token + */ + private String getMappedToken(String token, boolean hasNewMapping) { + String newToken = null; + HashMap tokens = null; + String blank = new String(""); + int localScope = functionBracePositions.size() - 1; + + String oldToken = getPreviousTokenMapping(token, hasNewMapping); + + if (!oldToken.equalsIgnoreCase(blank)) { + return oldToken; + } else if ((hasNewMapping || isInScopeChain(token))) { + lastTokenCount++; + newToken = new String("_" + Integer.toHexString(lastTokenCount)); + if (newToken.length() >= token.length()) { + newToken = token; + } + if (hasNewMapping) { + tokens = (HashMap) replacedTokens.get(localScope); + } else { + tokens = (HashMap) replacedTokens.get(parentScope); + } + + tokens.put(token, newToken); + return newToken; + } + return token; + } + + /** + * Checks for variable names in prototype chain + *
+ * + * @param token + * value of the string token + * @return boolean value indicating if the token is present in the chained + * scope + */ + private boolean isInScopeChain(String token) { + int scope = functionBracePositions.size(); + HashMap chainedScopeVars = (HashMap) functionVarMappings + .get(functionNum); + if (!chainedScopeVars.isEmpty()) { + for (int i = scope; i > 0; i--) { + if (chainedScopeVars.containsKey(new Integer(i))) { + parentScope = i - 1; + List temp = Arrays.asList((String[]) chainedScopeVars + .get(new Integer(i))); + if (temp.indexOf(token) != -1) { + return true; + } + } + } + } + return false; + } + + /** + * Checks previous token mapping + *
+ * + * @param token + * value of the string token + * @param hasNewMapping + * boolean value indicating a new variable binding + * @return string value of the previous token or blank string + */ + private String getPreviousTokenMapping(String token, boolean hasNewMapping) { + String result = new String(""); + int scope = replacedTokens.size() - 1; + + if (scope < 0) { + return result; + } + + if (hasNewMapping) { + HashMap tokens = (HashMap) (replacedTokens.get(scope)); + if (tokens.containsKey(token)) { + result = (String) tokens.get(token); + return result; + } + } else { + for (int i = scope; i > -1; i--) { + HashMap tokens = (HashMap) (replacedTokens.get(i)); + if (tokens.containsKey(token)) { + result = (String) tokens.get(token); + return result; + } + } + } + return result; + } + + /** + * Generate mappings for each Function node and parameters and variables + * names associated with it. + *
+ * + * @param parseTree + * Mapping for each function node and corresponding parameters & + * variables names + */ + private void collectFunctionMappings(ScriptOrFnNode parseTree) { + int level = -1; + collectFuncNodes(parseTree, level); + } + + /** + * Recursive method to traverse all Function nodes + *
+ * + * @param parseTree + * Mapping for each function node and corresponding parameters & + * variables names + * @param level + * scoping level + */ + private static void collectFuncNodes(ScriptOrFnNode parseTree, int level) { + level++; + functionVarMappings.add(new HashMap()); + + HashMap bindingNames = (HashMap) functionVarMappings + .get(functionVarMappings.size() - 1); + bindingNames.put(new Integer(level), parseTree.getParamAndVarNames()); + funcObjects.add(parseTree); + + int nestedCount = parseTree.getFunctionCount(); + for (int i = 0; i != nestedCount; ++i) { + collectFuncNodes(parseTree.getFunctionNode(i), level); + bindingNames = (HashMap) functionVarMappings + .get(functionVarMappings.size() - 1); + bindingNames.put(new Integer(level), parseTree + .getParamAndVarNames()); + } + } + + /** + * Compress the script + *
+ * + * @param encodedSource + * encoded source string + * @param offset + * position within the encoded source + * @param asQuotedString + * boolean value indicating a quoted string + * @param sb + * String buffer reference + * @param prevToken + * Previous token in encoded source + * @param inArgsList + * boolean value indicating position inside arguments list + * @param currentLevel + * embeded function level + * @param parseTree + * Mapping of each function node and corresponding parameters & + * variables names + * @return compressed script + */ + public int sourceCompress(String encodedSource, int offset, + boolean asQuotedString, StringBuffer sb, int prevToken, + boolean inArgsList, int currentLevel, ScriptOrFnNode parseTree) { + + boolean hasNewMapping = false; + + if (functionVarMappings.isEmpty()) + collectFunctionMappings(parseTree); + + int length = encodedSource.charAt(offset); + ++offset; + if ((0x8000 & length) != 0) { + length = ((0x7FFF & length) << 16) | encodedSource.charAt(offset); + ++offset; + } + if (sb != null) { + String str = encodedSource.substring(offset, offset + length); + String sourceStr = new String(str); + if ((prevToken == Token.VAR) || (inArgsList)) { + hasNewMapping = true; + } + if (((functionBracePositions.size() > 0) && (currentLevel >= (((Integer) functionBracePositions + .get(functionBracePositions.size() - 1)).intValue()))) + || (inArgsList)) { + if (prevToken != Token.DOT) { + str = this.getMappedToken(str, hasNewMapping); + } + } + if ((!inArgsList) && (asQuotedString)) { + if ((prevToken == Token.LC) || (prevToken == Token.COMMA)) { + str = sourceStr; + } + } + if (!asQuotedString) { + sb.append(str); + } else { + sb.append('"'); + sb.append(ScriptRuntime.escapeString(str)); + sb.append('"'); + } + } + return offset + length; + } + + public void enterNestingLevel(int braceNesting) { + functionBracePositions.add(new Integer(braceNesting + 1)); + replacedTokens.add(new HashMap()); + } + + public void leaveNestingLevel(int braceNesting) { + Integer bn = new Integer(braceNesting); + + if ((functionBracePositions.contains(bn)) + && (replacedTokens.size() > 0)) { + // remove our mappings now! + int scopedSize = replacedTokens.size(); + replacedTokens.remove(scopedSize - 1); + functionBracePositions.remove(bn); + } + } +} + + public class Decompiler { /** @@ -266,6 +537,515 @@ return new String(sourceBuffer, offset, sourceTop - offset); } + //Used to be private, but making it public so we + //can reset the token state between compression runs. + //Not very pretty. + public static TokenMapper tm = new TokenMapper(); + + /** + * Compress the script + *
+ * + * @param encodedSource encoded source string + * @param flags Flags specifying format of decompilation output + * @param properties Decompilation properties + * @param parseTree Mapping for each function node and corresponding parameters & variables names + * @return compressed script + */ + public static String compress(String encodedSource, int flags, + UintMap properties, ScriptOrFnNode parseTree){ + + int length = encodedSource.length(); + if (length == 0) { return ""; } + int indent = properties.getInt(INITIAL_INDENT_PROP, 0); + if (indent < 0) throw new IllegalArgumentException(); + int indentGap = properties.getInt(INDENT_GAP_PROP, 4); + if (indentGap < 0) throw new IllegalArgumentException(); + int caseGap = properties.getInt(CASE_GAP_PROP, 2); + if (caseGap < 0) throw new IllegalArgumentException(); + StringBuffer result = new StringBuffer(); + boolean justFunctionBody = (0 != (flags & Decompiler.ONLY_BODY_FLAG)); + boolean toSource = (0 != (flags & Decompiler.TO_SOURCE_FLAG)); + // Spew tokens in source, for debugging. + // as TYPE number char + if (printSource) { + System.err.println("length:" + length); + for (int i = 0; i < length; ++i) { + // Note that tokenToName will fail unless Context.printTrees + // is true. + String tokenname = null; + if (Token.printNames) { + tokenname = Token.name(encodedSource.charAt(i)); + } + if (tokenname == null) { + tokenname = "---"; + } + String pad = tokenname.length() > 7 + ? "\t" + : "\t\t"; + System.err.println + (tokenname + + pad + (int)encodedSource.charAt(i) + + "\t'" + ScriptRuntime.escapeString + (encodedSource.substring(i, i+1)) + + "'"); + } + System.err.println(); + } + int braceNesting = 0; + boolean afterFirstEOL = false; + int i = 0; + int prevToken = 0; + boolean primeFunctionNesting = false; + boolean inArgsList = false; + boolean primeInArgsList = false; + int topFunctionType; + if (encodedSource.charAt(i) == Token.SCRIPT) { + ++i; + topFunctionType = -1; + } else { + topFunctionType = encodedSource.charAt(i + 1); + } + if (!toSource) { + // add an initial newline to exactly match js. + // result.append('\n'); + for (int j = 0; j < indent; j++){ + // result.append(' '); + result.append(""); + } + } else { + if (topFunctionType == FunctionNode.FUNCTION_EXPRESSION) { + result.append('('); + } + } + while (i < length) { + if(i>0){ + prevToken = encodedSource.charAt(i-1); + } + // System.out.println(Token.name(getNext(source, length, i))); + switch(encodedSource.charAt(i)) { + case Token.NAME: + case Token.REGEXP: // re-wrapped in '/'s in parser... + int jumpPos = getSourceStringEnd(encodedSource, i+1); + if(Token.OBJECTLIT == encodedSource.charAt(jumpPos)){ + i = printSourceString(encodedSource, i + 1, false, result); + }else{ + i = tm.sourceCompress( encodedSource, i + 1, false, result, prevToken, + inArgsList, braceNesting, parseTree); + } + continue; + case Token.STRING: + i = printSourceString(encodedSource, i + 1, true, result); + continue; + case Token.NUMBER: + i = printSourceNumber(encodedSource, i + 1, result); + continue; + case Token.TRUE: + result.append("true"); + break; + case Token.FALSE: + result.append("false"); + break; + case Token.NULL: + result.append("null"); + break; + case Token.THIS: + result.append("this"); + break; + case Token.FUNCTION: + ++i; // skip function type + tm.functionNum++; + primeInArgsList = true; + primeFunctionNesting = true; + result.append("function"); + if (Token.LP != getNext(encodedSource, length, i)) { + result.append(' '); + } + break; + case FUNCTION_END: + // Do nothing + break; + case Token.COMMA: + result.append(","); + break; + case Token.LC: + ++braceNesting; + if (Token.EOL == getNext(encodedSource, length, i)){ + indent += indentGap; + } + result.append('{'); + // // result.append('\n'); + break; + case Token.RC: { + tm.leaveNestingLevel(braceNesting); + --braceNesting; + /* don't print the closing RC if it closes the + * toplevel function and we're called from + * decompileFunctionBody. + */ + if(justFunctionBody && braceNesting == 0){ + break; + } + // // result.append('\n'); + result.append('}'); + // // result.append(' '); + switch (getNext(encodedSource, length, i)) { + case Token.EOL: + case FUNCTION_END: + indent -= indentGap; + break; + case Token.WHILE: + case Token.ELSE: + indent -= indentGap; + // result.append(' '); + result.append(""); + break; + } + break; + } + case Token.LP: + if(primeInArgsList){ + inArgsList = true; + primeInArgsList = false; + } + if(primeFunctionNesting){ + tm.enterNestingLevel(braceNesting); + primeFunctionNesting = false; + } + result.append('('); + break; + case Token.RP: + if(inArgsList){ + inArgsList = false; + } + result.append(')'); + /* + if (Token.LC == getNext(source, length, i)){ + result.append(' '); + } + */ + break; + case Token.LB: + result.append('['); + break; + case Token.RB: + result.append(']'); + break; + case Token.EOL: { + if (toSource) break; + boolean newLine = true; + if (!afterFirstEOL) { + afterFirstEOL = true; + if (justFunctionBody) { + /* throw away just added 'function name(...) {' + * and restore the original indent + */ + result.setLength(0); + indent -= indentGap; + newLine = false; + } + } + if (newLine) { + result.append('\n'); + } + /* + */ + /* add indent if any tokens remain, + * less setback if next token is + * a label, case or default. + */ + if (i + 1 < length) { + int less = 0; + int nextToken = encodedSource.charAt(i + 1); + if (nextToken == Token.CASE + || nextToken == Token.DEFAULT) + { + less = indentGap - caseGap; + } else if (nextToken == Token.RC) { + less = indentGap; + } + /* elaborate check against label... skip past a + * following inlined NAME and look for a COLON. + */ + else if (nextToken == Token.NAME) { + int afterName = getSourceStringEnd(encodedSource, i + 2); + if (encodedSource.charAt(afterName) == Token.COLON) + less = indentGap; + } + for (; less < indent; less++){ + // result.append(' '); + result.append(""); + } + } + break; + } + case Token.DOT: + result.append('.'); + break; + case Token.NEW: + result.append("new "); + break; + case Token.DELPROP: + result.append("delete "); + break; + case Token.IF: + result.append("if"); + break; + case Token.ELSE: + result.append("else"); + break; + case Token.FOR: + result.append("for"); + break; + case Token.IN: + result.append(" in "); + break; + case Token.WITH: + result.append("with"); + break; + case Token.WHILE: + result.append("while"); + break; + case Token.DO: + result.append("do"); + break; + case Token.TRY: + result.append("try"); + break; + case Token.CATCH: + result.append("catch"); + break; + case Token.FINALLY: + result.append("finally"); + break; + case Token.THROW: + result.append("throw "); + break; + case Token.SWITCH: + result.append("switch"); + break; + case Token.BREAK: + result.append("break"); + if(Token.NAME == getNext(encodedSource, length, i)){ + result.append(' '); + } + break; + case Token.CONTINUE: + result.append("continue"); + if(Token.NAME == getNext(encodedSource, length, i)){ + result.append(' '); + } + break; + case Token.CASE: + result.append("case "); + break; + case Token.DEFAULT: + result.append("default"); + break; + case Token.RETURN: + result.append("return"); + if(Token.SEMI != getNext(encodedSource, length, i)){ + result.append(' '); + } + break; + case Token.VAR: + result.append("var "); + break; + case Token.SEMI: + result.append(';'); + // result.append('\n'); + /* + if (Token.EOL != getNext(source, length, i)) { + // separators in FOR + result.append(' '); + } + */ + break; + case Token.ASSIGN: + result.append("="); + break; + case Token.ASSIGN_ADD: + result.append("+="); + break; + case Token.ASSIGN_SUB: + result.append("-="); + break; + case Token.ASSIGN_MUL: + result.append("*="); + break; + case Token.ASSIGN_DIV: + result.append("/="); + break; + case Token.ASSIGN_MOD: + result.append("%="); + break; + case Token.ASSIGN_BITOR: + result.append("|="); + break; + case Token.ASSIGN_BITXOR: + result.append("^="); + break; + case Token.ASSIGN_BITAND: + result.append("&="); + break; + case Token.ASSIGN_LSH: + result.append("<<="); + break; + case Token.ASSIGN_RSH: + result.append(">>="); + break; + case Token.ASSIGN_URSH: + result.append(">>>="); + break; + case Token.HOOK: + result.append("?"); + break; + case Token.OBJECTLIT: + // pun OBJECTLIT to mean colon in objlit property + // initialization. + // This needs to be distinct from COLON in the general case + // to distinguish from the colon in a ternary... which needs + // different spacing. + result.append(':'); + break; + case Token.COLON: + if (Token.EOL == getNext(encodedSource, length, i)) + // it's the end of a label + result.append(':'); + else + // it's the middle part of a ternary + result.append(":"); + break; + case Token.OR: + result.append("||"); + break; + case Token.AND: + result.append("&&"); + break; + case Token.BITOR: + result.append("|"); + break; + case Token.BITXOR: + result.append("^"); + break; + case Token.BITAND: + result.append("&"); + break; + case Token.SHEQ: + result.append("==="); + break; + case Token.SHNE: + result.append("!=="); + break; + case Token.EQ: + result.append("=="); + break; + case Token.NE: + result.append("!="); + break; + case Token.LE: + result.append("<="); + break; + case Token.LT: + result.append("<"); + break; + case Token.GE: + result.append(">="); + break; + case Token.GT: + result.append(">"); + break; + case Token.INSTANCEOF: + // FIXME: does this really need leading space? + result.append(" instanceof "); + break; + case Token.LSH: + result.append("<<"); + break; + case Token.RSH: + result.append(">>"); + break; + case Token.URSH: + result.append(">>>"); + break; + case Token.TYPEOF: + result.append("typeof "); + break; + case Token.VOID: + result.append("void "); + break; + case Token.NOT: + result.append('!'); + break; + case Token.BITNOT: + result.append('~'); + break; + case Token.POS: + result.append('+'); + break; + case Token.NEG: + result.append('-'); + break; + case Token.INC: + if(Token.ADD == prevToken){ + result.append(' '); + } + result.append("++"); + if(Token.ADD == getNext(encodedSource, length, i)){ + result.append(' '); + } + break; + case Token.DEC: + if(Token.SUB == prevToken){ + result.append(' '); + } + result.append("--"); + if(Token.SUB == getNext(encodedSource, length, i)){ + result.append(' '); + } + break; + case Token.ADD: + result.append("+"); + break; + case Token.SUB: + result.append("-"); + break; + case Token.MUL: + result.append("*"); + break; + case Token.DIV: + result.append("/"); + break; + case Token.MOD: + result.append("%"); + break; + case Token.COLONCOLON: + result.append("::"); + break; + case Token.DOTDOT: + result.append(".."); + break; + case Token.XMLATTR: + result.append('@'); + break; + default: + // If we don't know how to decompile it, raise an exception. + throw new RuntimeException(); + } + ++i; + } + if (!toSource) { + // add that trailing newline if it's an outermost function. + // if (!justFunctionBody){ + // result.append('\n'); + // } + } else { + if (topFunctionType == FunctionNode.FUNCTION_EXPRESSION) { + result.append(')'); + } + } + return result.toString(); + } /** * Decompile the source information associated with this js * function/script back into a string. For the most part, this Index: toolsrc/org/mozilla/javascript/tools/resources/Messages.properties =================================================================== RCS file: /cvsroot/mozilla/js/rhino/toolsrc/org/mozilla/javascript/tools/resources/Messages.properties,v retrieving revision 1.22 diff -u -r1.22 Messages.properties --- toolsrc/org/mozilla/javascript/tools/resources/Messages.properties 2 Sep 2005 14:18:40 -0000 1.22 +++ toolsrc/org/mozilla/javascript/tools/resources/Messages.properties 2 Apr 2007 22:06:50 -0000 @@ -55,7 +55,9 @@ \ -version 100|110|120|130|140|150\n\ \ -opt [-1|0-9]\n\ \ -f script-filename\n\ - \ -e script-source + \ -e script-source\n\ + \ -o output-filename\n\ + \ -c script-filename msg.help =\ Index: toolsrc/org/mozilla/javascript/tools/shell/Main.java =================================================================== RCS file: /cvsroot/mozilla/js/rhino/toolsrc/org/mozilla/javascript/tools/shell/Main.java,v retrieving revision 1.65 diff -u -r1.65 Main.java --- toolsrc/org/mozilla/javascript/tools/shell/Main.java 30 Sep 2005 08:28:51 -0000 1.65 +++ toolsrc/org/mozilla/javascript/tools/shell/Main.java 2 Apr 2007 22:06:50 -0000 @@ -66,6 +66,11 @@ static private final int EXITCODE_RUNTIME_ERROR = 3; static private final int EXITCODE_FILE_NOT_FOUND = 4; static boolean processStdin = true; + static boolean outputCompressed = false; + static String outputFileName = "dojo.js.compressed.js"; + static boolean isOutputFileSet = false; + + static Vector fileList = new Vector(5); private static SecurityProxy securityImpl; @@ -260,6 +265,20 @@ shellContextFactory.call(iproxy); continue; } + if (arg.equals("-c")) { + outputCompressed = true; + continue; + } + if (arg.equals("-o") && ++i < args.length) { + if (args[i].startsWith("-") || args[i].length() == 0) + { + usageError = arg; + break goodUsage; + } + isOutputFileSet = true; + outputFileName = args[i]; + continue; + } if (arg.equals("-w")) { errorReporter.setIsReportingWarnings(true); continue; @@ -333,7 +352,6 @@ int lineno = 1; boolean hitEOF = false; while (!hitEOF) { - int startline = lineno; if (filename == null) ps.print("js> "); ps.flush(); @@ -395,10 +413,12 @@ String path, Object securityDomain) { Script script; + String cout = null; + String source = null; if (path.endsWith(".class")) { script = loadCompiledScript(cx, path, securityDomain); } else { - String source = (String)readFileOrUrl(path, true); + source = (String)readFileOrUrl(path, true); if (source == null) { exitCode = EXITCODE_FILE_NOT_FOUND; return; @@ -418,9 +438,58 @@ } script = loadScriptFromSource(cx, source, path, 1, securityDomain); } - if (script != null) { - evaluateScript(script, cx, scope); - } + if ((script != null) && (source != null)) { + if (outputCompressed) { + cout = compressScript(cx, scope, script, source, path, 1, + securityDomain); + if (isOutputFileSet) { + try { + BufferedWriter out = new BufferedWriter(new FileWriter( + outputFileName)); + out.write(cout); + out.close(); + } catch (IOException ex) { + Context.reportError(ex.toString()); + } + System.out.println("Compressed file stored in '" + outputFileName + "'"); + } else { + global.getOut().println(cout); + } + + }else{ + evaluateScript(script, cx, scope); + } + } + } + + public static String compressScript(Context cx, Scriptable scope, Script script, String source, String sourceName, int lineno, Object securityDomain) + { + String compressedSource = null; + try { + if (script != null) { + compressedSource = cx.compressReader(scope, script, source, sourceName, + lineno, securityDomain); + } else { + compressedSource = source; + } + } catch (WrappedException we) { + global.getErr().println(we.getWrappedException().toString()); + we.printStackTrace(); + } catch (EvaluatorException ee) { + // Already printed message. + exitCode = EXITCODE_RUNTIME_ERROR; + } catch (RhinoException rex) { + errorReporter.reportException(rex); + exitCode = EXITCODE_RUNTIME_ERROR; + } catch (VirtualMachineError ex) { + // Treat StackOverflow and OutOfMemory as runtime errors + ex.printStackTrace(); + String msg = ToolErrorReporter.getMessage( + "msg.uncaughtJSException", ex.toString()); + exitCode = EXITCODE_RUNTIME_ERROR; + Context.reportError(msg); + } + return compressedSource; } public static Script loadScriptFromSource(Context cx, String scriptSource,