/// <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();
}