/// <summary> /// Decompile the source information associated with this js /// function/script back into a string. /// </summary> /// <remarks> /// Decompile the source information associated with this js /// function/script back into a string. For the most part, this /// just means translating tokens back to their string /// representations; there's a little bit of lookahead logic to /// decide the proper spacing/indentation. Most of the work in /// mapping the original source to the prettyprinted decompiled /// version is done by the parser. /// </remarks> /// <param name="source">encoded source tree presentation</param> /// <param name="flags">flags to select output format</param> /// <param name="properties">indentation properties</param> public static string Decompile(string source, int flags, UintMap properties) { int length = source.Length; if (length == 0) { return string.Empty; } int indent = properties.GetInt(INITIAL_INDENT_PROP, 0); if (indent < 0) { throw new ArgumentException(); } int indentGap = properties.GetInt(INDENT_GAP_PROP, 4); if (indentGap < 0) { throw new ArgumentException(); } int caseGap = properties.GetInt(CASE_GAP_PROP, 2); if (caseGap < 0) { throw new ArgumentException(); } StringBuilder result = new StringBuilder(); bool justFunctionBody = (0 != (flags & Decompiler.ONLY_BODY_FLAG)); bool toSource = (0 != (flags & Decompiler.TO_SOURCE_FLAG)); // Spew tokens in source, for debugging. // as TYPE number char // Note that tokenToName will fail unless Context.printTrees // is true. int braceNesting = 0; bool afterFirstEOL = false; int i = 0; int topFunctionType; if (source[i] == Token.SCRIPT) { ++i; topFunctionType = -1; } else { topFunctionType = source[i + 1]; } if (!toSource) { // add an initial newline to exactly match js. result.Append('\n'); for (int j = 0; j < indent; j++) { result.Append(' '); } } else { if (topFunctionType == FunctionNode.FUNCTION_EXPRESSION) { result.Append('('); } } while (i < length) { switch ((int)source[i]) { case Token.GET: case Token.SET: { result.Append(source[i] == Token.GET ? "get " : "set "); ++i; i = PrintSourceString(source, i + 1, false, result); // Now increment one more to get past the FUNCTION token ++i; break; } case Token.NAME: case Token.REGEXP: { // re-wrapped in '/'s in parser... i = PrintSourceString(source, i + 1, false, result); continue; } case Token.STRING: { i = PrintSourceString(source, i + 1, true, result); continue; } case Token.NUMBER: { i = PrintSourceNumber(source, 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 result.Append("function "); break; } case FUNCTION_END: { // Do nothing break; } case Token.COMMA: { result.Append(", "); break; } case Token.LC: { ++braceNesting; if (Token.EOL == GetNext(source, length, i)) { indent += indentGap; } result.Append('{'); break; } case Token.RC: { --braceNesting; if (justFunctionBody && braceNesting == 0) { break; } result.Append('}'); switch (GetNext(source, length, i)) { case Token.EOL: case FUNCTION_END: { indent -= indentGap; break; } case Token.WHILE: case Token.ELSE: { indent -= indentGap; result.Append(' '); break; } } break; } case Token.LP: { result.Append('('); break; } case Token.RP: { 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; } bool newLine = true; if (!afterFirstEOL) { afterFirstEOL = true; if (justFunctionBody) { result.Length = 0; indent -= indentGap; newLine = false; } } if (newLine) { result.Append('\n'); } if (i + 1 < length) { int less = 0; int nextToken = source[i + 1]; if (nextToken == Token.CASE || nextToken == Token.DEFAULT) { less = indentGap - caseGap; } else { if (nextToken == Token.RC) { less = indentGap; } else { if (nextToken == Token.NAME) { int afterName = GetSourceStringEnd(source, i + 2); if (source[afterName] == Token.COLON) { less = indentGap; } } } } for (; less < indent; less++) { 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(source, length, i)) { result.Append(' '); } break; } case Token.CONTINUE: { result.Append("continue"); if (Token.NAME == GetNext(source, 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(source, length, i)) { result.Append(' '); } break; } case Token.VAR: { result.Append("var "); break; } case Token.LET: { result.Append("let "); break; } case Token.SEMI: { result.Append(';'); 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(source, 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: { 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.CONST: { result.Append("const "); break; } case Token.YIELD: { result.Append("yield "); 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: { result.Append("++"); break; } case Token.DEC: { 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.DOTQUERY: { result.Append(".("); break; } case Token.XMLATTR: { result.Append('@'); break; } case Token.DEBUGGER: { result.Append("debugger;\n"); break; } default: { // If we don't know how to decompile it, raise an exception. throw new Exception("Token: " + Token.Name(source[i])); } } ++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(); }