I moved decompilation code from NativeFunction to Parser so changes in compressed source structure will affect only one file.

This commit is contained in:
igor%mir2.org 2002-11-26 08:00:44 +00:00
parent 14fe83e437
commit fa14609cd7
2 changed files with 693 additions and 679 deletions

View File

@ -45,30 +45,7 @@ package org.mozilla.javascript;
*/
public class NativeFunction extends BaseFunction {
// how much to indent
private final static int OFFSET = 4;
// less how much for case labels
private final static int SETBACK = 2;
// whether to do a debug print of the source information, when
// decompiling.
private static final boolean printSource = false;
/**
* 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.
*
* Note that support for Context.decompileFunctionBody is hacked
* on through special cases; I suspect that js makes a distinction
* between function header and function body that rhino
* decompilation does not.
*
* @param cx Current context
*
* @param indent How much to indent the decompiled result
@ -78,630 +55,11 @@ public class NativeFunction extends BaseFunction {
*/
public String decompile(Context cx, int indent, boolean justbody) {
Object[] srcData = new Object[1];
StringBuffer result = new StringBuffer();
decompile_r(this, indent, true, justbody, srcData, result);
return result.toString();
}
private static void decompile_r(NativeFunction f, int indent,
boolean toplevel, boolean justbody,
Object[] srcData, StringBuffer result)
{
String source = f.source;
if (source == null) {
if (!justbody) {
result.append("function ");
result.append(f.getFunctionName());
result.append("() {\n\t");
}
result.append("[native code]\n");
if (!justbody) {
result.append("}\n");
}
return;
return super.decompile(cx, indent, justbody);
} else {
return Parser.decompile(this, version, indent, justbody);
}
int length = source.length();
// 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 = TokenStream.tokenToName(source.charAt(i));
if (tokenname == null)
tokenname = "---";
String pad = tokenname.length() > 7
? "\t"
: "\t\t";
System.err.println
(tokenname
+ pad + (int)source.charAt(i)
+ "\t'" + ScriptRuntime.escapeString
(source.substring(i, i+1))
+ "'");
}
System.err.println();
}
int i = 0;
if (length != 0) {
// If the first token is TokenStream.SCRIPT, then we're
// decompiling the toplevel script, otherwise it a function
// and should start with TokenStream.FUNCTION
if (toplevel) {
// add an initial newline to exactly match js.
if (!justbody)
result.append('\n');
for (int j = 0; j < indent; j++)
result.append(' ');
}
int token = source.charAt(i);
++i;
if (token == TokenStream.FUNCTION) {
if (!justbody) {
result.append("function ");
/* version != 1.2 Function constructor behavior - if
* there's no function name in the source info, and
* the names[0] entry is the empty string, then it must
* have been created by the Function constructor;
* print 'anonymous' as the function name if the
* version (under which the function was compiled) is
* less than 1.2... or if it's greater than 1.2, because
* we need to be closer to ECMA. (ToSource, please?)
*/
if (source.charAt(i) == TokenStream.LP
&& f.version != Context.VERSION_1_2
&& f.functionName != null
&& f.functionName.equals("anonymous"))
{
result.append("anonymous");
}
} else {
// Skip past the entire function header pass the next EOL.
skipLoop: for (;;) {
token = source.charAt(i);
++i;
switch (token) {
case TokenStream.EOL:
break skipLoop;
case TokenStream.NAME:
// Skip function or argument name
i = Parser.getSourceString(source, i, null);
break;
case TokenStream.LP:
case TokenStream.COMMA:
case TokenStream.RP:
break;
default:
// Bad function header
throw new RuntimeException();
}
}
}
} else if (token != TokenStream.SCRIPT) {
// Bad source header
throw new RuntimeException();
}
}
while (i < length) {
switch(source.charAt(i)) {
case TokenStream.NAME:
case TokenStream.REGEXP: // re-wrapped in '/'s in parser...
/* NAMEs are encoded as NAME, (char) length, string...
* Note that lookahead for detecting labels depends on
* this encoding; change there if this changes.
* Also change function-header skipping code above,
* used when decompling under decompileFunctionBody.
*/
i = Parser.getSourceString(source, i + 1, srcData);
result.append((String)srcData[0]);
continue;
case TokenStream.NUMBER: {
i = Parser.getSourceNumber(source, i + 1, srcData);
double number = ((Number)srcData[0]).doubleValue();
result.append(ScriptRuntime.numberToString(number, 10));
continue;
}
case TokenStream.STRING:
i = Parser.getSourceString(source, i + 1, srcData);
result.append('"');
result.append(ScriptRuntime.escapeString((String)srcData[0]));
result.append('"');
continue;
case TokenStream.PRIMARY:
++i;
switch(source.charAt(i)) {
case TokenStream.TRUE:
result.append("true");
break;
case TokenStream.FALSE:
result.append("false");
break;
case TokenStream.NULL:
result.append("null");
break;
case TokenStream.THIS:
result.append("this");
break;
case TokenStream.TYPEOF:
result.append("typeof");
break;
case TokenStream.VOID:
result.append("void");
break;
case TokenStream.UNDEFINED:
result.append("undefined");
break;
}
break;
case TokenStream.FUNCTION: {
/* decompile a FUNCTION token as an escape; call
* toString on the nth enclosed nested function,
* where n is given by the byte that follows.
*/
++i;
int functionNumber = source.charAt(i);
if (f.nestedFunctions == null
|| functionNumber > f.nestedFunctions.length)
{
String message;
if (f.functionName != null && f.functionName.length() > 0) {
message = Context.getMessage2
("msg.no.function.ref.found.in",
new Integer(functionNumber), f.functionName);
} else {
message = Context.getMessage1
("msg.no.function.ref.found",
new Integer(functionNumber));
}
throw Context.reportRuntimeError(message);
}
decompile_r(f.nestedFunctions[functionNumber], indent,
false, false, srcData, result);
break;
}
case TokenStream.COMMA:
result.append(", ");
break;
case TokenStream.LC:
if (nextIs(source, length, i, TokenStream.EOL))
indent += OFFSET;
result.append('{');
break;
case TokenStream.RC:
/* don't print the closing RC if it closes the
* toplevel function and we're called from
* decompileFunctionBody.
*/
if (justbody && toplevel && i + 1 == length)
break;
if (nextIs(source, length, i, TokenStream.EOL))
indent -= OFFSET;
if (nextIs(source, length, i, TokenStream.WHILE)
|| nextIs(source, length, i, TokenStream.ELSE)) {
indent -= OFFSET;
result.append("} ");
}
else
result.append('}');
break;
case TokenStream.LP:
result.append('(');
break;
case TokenStream.RP:
if (nextIs(source, length, i, TokenStream.LC))
result.append(") ");
else
result.append(')');
break;
case TokenStream.LB:
result.append('[');
break;
case TokenStream.RB:
result.append(']');
break;
case TokenStream.EOL:
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 = source.charAt(i + 1);
if (nextToken == TokenStream.CASE
|| nextToken == TokenStream.DEFAULT)
less = SETBACK;
else if (nextToken == TokenStream.RC)
less = OFFSET;
/* elaborate check against label... skip past a
* following inlined NAME and look for a COLON.
* Depends on how NAME is encoded.
*/
else if (nextToken == TokenStream.NAME) {
int afterName = Parser.getSourceString(source, i + 2,
null);
if (source.charAt(afterName) == TokenStream.COLON)
less = OFFSET;
}
for (; less < indent; less++)
result.append(' ');
}
break;
case TokenStream.DOT:
result.append('.');
break;
case TokenStream.NEW:
result.append("new ");
break;
case TokenStream.DELPROP:
result.append("delete ");
break;
case TokenStream.IF:
result.append("if ");
break;
case TokenStream.ELSE:
result.append("else ");
break;
case TokenStream.FOR:
result.append("for ");
break;
case TokenStream.IN:
result.append(" in ");
break;
case TokenStream.WITH:
result.append("with ");
break;
case TokenStream.WHILE:
result.append("while ");
break;
case TokenStream.DO:
result.append("do ");
break;
case TokenStream.TRY:
result.append("try ");
break;
case TokenStream.CATCH:
result.append("catch ");
break;
case TokenStream.FINALLY:
result.append("finally ");
break;
case TokenStream.THROW:
result.append("throw ");
break;
case TokenStream.SWITCH:
result.append("switch ");
break;
case TokenStream.BREAK:
if (nextIs(source, length, i, TokenStream.NAME))
result.append("break ");
else
result.append("break");
break;
case TokenStream.CONTINUE:
if (nextIs(source, length, i, TokenStream.NAME))
result.append("continue ");
else
result.append("continue");
break;
case TokenStream.CASE:
result.append("case ");
break;
case TokenStream.DEFAULT:
result.append("default");
break;
case TokenStream.RETURN:
if (nextIs(source, length, i, TokenStream.SEMI))
result.append("return");
else
result.append("return ");
break;
case TokenStream.VAR:
result.append("var ");
break;
case TokenStream.SEMI:
if (nextIs(source, length, i, TokenStream.EOL))
// statement termination
result.append(';');
else
// separators in FOR
result.append("; ");
break;
case TokenStream.ASSIGN:
++i;
switch(source.charAt(i)) {
case TokenStream.NOP:
result.append(" = ");
break;
case TokenStream.ADD:
result.append(" += ");
break;
case TokenStream.SUB:
result.append(" -= ");
break;
case TokenStream.MUL:
result.append(" *= ");
break;
case TokenStream.DIV:
result.append(" /= ");
break;
case TokenStream.MOD:
result.append(" %= ");
break;
case TokenStream.BITOR:
result.append(" |= ");
break;
case TokenStream.BITXOR:
result.append(" ^= ");
break;
case TokenStream.BITAND:
result.append(" &= ");
break;
case TokenStream.LSH:
result.append(" <<= ");
break;
case TokenStream.RSH:
result.append(" >>= ");
break;
case TokenStream.URSH:
result.append(" >>>= ");
break;
}
break;
case TokenStream.HOOK:
result.append(" ? ");
break;
case TokenStream.OBJLIT:
// pun OBJLIT 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 TokenStream.COLON:
if (nextIs(source, length, i, TokenStream.EOL))
// it's the end of a label
result.append(':');
else
// it's the middle part of a ternary
result.append(" : ");
break;
case TokenStream.OR:
result.append(" || ");
break;
case TokenStream.AND:
result.append(" && ");
break;
case TokenStream.BITOR:
result.append(" | ");
break;
case TokenStream.BITXOR:
result.append(" ^ ");
break;
case TokenStream.BITAND:
result.append(" & ");
break;
case TokenStream.EQOP:
++i;
switch(source.charAt(i)) {
case TokenStream.SHEQ:
/*
* Emulate the C engine; if we're under version
* 1.2, then the == operator behaves like the ===
* operator (and the source is generated by
* decompiling a === opcode), so print the ===
* operator as ==.
*/
result.append(f.version == Context.VERSION_1_2
? " == " : " === ");
break;
case TokenStream.SHNE:
result.append(f.version == Context.VERSION_1_2
? " != " : " !== ");
break;
case TokenStream.EQ:
result.append(" == ");
break;
case TokenStream.NE:
result.append(" != ");
break;
}
break;
case TokenStream.RELOP:
++i;
switch(source.charAt(i)) {
case TokenStream.LE:
result.append(" <= ");
break;
case TokenStream.LT:
result.append(" < ");
break;
case TokenStream.GE:
result.append(" >= ");
break;
case TokenStream.GT:
result.append(" > ");
break;
case TokenStream.INSTANCEOF:
result.append(" instanceof ");
break;
}
break;
case TokenStream.SHOP:
++i;
switch(source.charAt(i)) {
case TokenStream.LSH:
result.append(" << ");
break;
case TokenStream.RSH:
result.append(" >> ");
break;
case TokenStream.URSH:
result.append(" >>> ");
break;
}
break;
case TokenStream.UNARYOP:
++i;
switch(source.charAt(i)) {
case TokenStream.TYPEOF:
result.append("typeof ");
break;
case TokenStream.VOID:
result.append("void ");
break;
case TokenStream.NOT:
result.append('!');
break;
case TokenStream.BITNOT:
result.append('~');
break;
case TokenStream.ADD:
result.append('+');
break;
case TokenStream.SUB:
result.append('-');
break;
}
break;
case TokenStream.INC:
result.append("++");
break;
case TokenStream.DEC:
result.append("--");
break;
case TokenStream.ADD:
result.append(" + ");
break;
case TokenStream.SUB:
result.append(" - ");
break;
case TokenStream.MUL:
result.append(" * ");
break;
case TokenStream.DIV:
result.append(" / ");
break;
case TokenStream.MOD:
result.append(" % ");
break;
default:
// If we don't know how to decompile it, raise an exception.
throw new RuntimeException();
}
++i;
}
// add that trailing newline if it's an outermost function.
if (toplevel && !justbody)
result.append('\n');
}
private static boolean nextIs(String source, int length, int i, int token) {
return (i + 1 < length) ? source.charAt(i + 1) == token : false;
}
public int getLength() {
@ -719,6 +77,7 @@ public class NativeFunction extends BaseFunction {
}
/**
* @deprecated Use {@link #getFunctionName()} instead.
* For backwards compatibility keep an old method name used by
* Batik and possibly others.
*/
@ -745,7 +104,7 @@ public class NativeFunction extends BaseFunction {
/**
* An array of NativeFunction values for each nested function.
* Used internally, and also for decompiling nested functions.
* Used only for decompiling of nested functions.
*/
public NativeFunction[] nestedFunctions;

View File

@ -1491,19 +1491,6 @@ class Parser {
sourceTop = nextTop;
}
static int getSourceString(String source, int offset, Object[] result) {
int length = source.charAt(offset);
++offset;
if ((0x8000 & length) != 0) {
length = ((0x7FFF & length) << 16) | source.charAt(offset);
++offset;
}
if (result != null) {
result[0] = source.substring(offset, offset + length);
}
return offset + length;
}
private void sourceAddNumber(double n) {
sourceAdd((char)TokenStream.NUMBER);
@ -1556,7 +1543,683 @@ class Parser {
}
}
static int getSourceNumber(String source, int offset, Object[] result) {
private void increaseSourceCapacity(int minimalCapacity) {
// Call this only when capacity increase is must
if (Context.check && minimalCapacity <= sourceBuffer.length)
Context.codeBug();
int newCapacity = sourceBuffer.length * 2;
if (newCapacity < minimalCapacity) {
newCapacity = minimalCapacity;
}
char[] tmp = new char[newCapacity];
System.arraycopy(sourceBuffer, 0, tmp, 0, sourceTop);
sourceBuffer = tmp;
}
private String sourceToString(int offset) {
if (Context.check && (offset < 0 || sourceTop < offset))
Context.codeBug();
return new String(sourceBuffer, offset, sourceTop - offset);
}
/**
* 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.
*
* Note that support for Context.decompileFunctionBody is hacked
* on through special cases; I suspect that js makes a distinction
* between function header and function body that rhino
* decompilation does not.
*
* @param f function to decompile
*
* @param version engine version used to compile the source
*
* @param indent How much to indent the decompiled result
*
* @param justbody Whether the decompilation should omit the
* function header and trailing brace.
*/
static String decompile(NativeFunction f, int version,
int indent, boolean justbody)
{
StringBuffer result = new StringBuffer();
Object[] srcData = new Object[1];
decompile_r(f, version, indent, true, justbody, srcData, result);
return result.toString();
}
private static void decompile_r(NativeFunction f, int version, int indent,
boolean toplevel, boolean justbody,
Object[] srcData, StringBuffer result)
{
String source = f.source;
int length = source.length();
String functionName = f.functionName;
NativeFunction[] nestedFunctions = f.nestedFunctions;
// 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 = TokenStream.tokenToName(source.charAt(i));
if (tokenname == null)
tokenname = "---";
String pad = tokenname.length() > 7
? "\t"
: "\t\t";
System.err.println
(tokenname
+ pad + (int)source.charAt(i)
+ "\t'" + ScriptRuntime.escapeString
(source.substring(i, i+1))
+ "'");
}
System.err.println();
}
int i = 0;
if (length != 0) {
// If the first token is TokenStream.SCRIPT, then we're
// decompiling the toplevel script, otherwise it a function
// and should start with TokenStream.FUNCTION
if (toplevel) {
// add an initial newline to exactly match js.
if (!justbody)
result.append('\n');
for (int j = 0; j < indent; j++)
result.append(' ');
}
int token = source.charAt(i);
++i;
if (token == TokenStream.FUNCTION) {
if (!justbody) {
result.append("function ");
/* version != 1.2 Function constructor behavior - if
* there's no function name in the source info, and
* the names[0] entry is the empty string, then it must
* have been created by the Function constructor;
* print 'anonymous' as the function name if the
* version (under which the function was compiled) is
* less than 1.2... or if it's greater than 1.2, because
* we need to be closer to ECMA. (ToSource, please?)
*/
if (source.charAt(i) == TokenStream.LP
&& version != Context.VERSION_1_2
&& functionName != null
&& functionName.equals("anonymous"))
{
result.append("anonymous");
}
} else {
// Skip past the entire function header pass the next EOL.
skipLoop: for (;;) {
token = source.charAt(i);
++i;
switch (token) {
case TokenStream.EOL:
break skipLoop;
case TokenStream.NAME:
// Skip function or argument name
i = Parser.getSourceString(source, i, null);
break;
case TokenStream.LP:
case TokenStream.COMMA:
case TokenStream.RP:
break;
default:
// Bad function header
throw new RuntimeException();
}
}
}
} else if (token != TokenStream.SCRIPT) {
// Bad source header
throw new RuntimeException();
}
}
while (i < length) {
switch(source.charAt(i)) {
case TokenStream.NAME:
case TokenStream.REGEXP: // re-wrapped in '/'s in parser...
/* NAMEs are encoded as NAME, (char) length, string...
* Note that lookahead for detecting labels depends on
* this encoding; change there if this changes.
* Also change function-header skipping code above,
* used when decompling under decompileFunctionBody.
*/
i = Parser.getSourceString(source, i + 1, srcData);
result.append((String)srcData[0]);
continue;
case TokenStream.NUMBER: {
i = Parser.getSourceNumber(source, i + 1, srcData);
double number = ((Number)srcData[0]).doubleValue();
result.append(ScriptRuntime.numberToString(number, 10));
continue;
}
case TokenStream.STRING:
i = Parser.getSourceString(source, i + 1, srcData);
result.append('"');
result.append(ScriptRuntime.escapeString((String)srcData[0]));
result.append('"');
continue;
case TokenStream.PRIMARY:
++i;
switch(source.charAt(i)) {
case TokenStream.TRUE:
result.append("true");
break;
case TokenStream.FALSE:
result.append("false");
break;
case TokenStream.NULL:
result.append("null");
break;
case TokenStream.THIS:
result.append("this");
break;
case TokenStream.TYPEOF:
result.append("typeof");
break;
case TokenStream.VOID:
result.append("void");
break;
case TokenStream.UNDEFINED:
result.append("undefined");
break;
}
break;
case TokenStream.FUNCTION: {
/* decompile a FUNCTION token as an escape; call
* toString on the nth enclosed nested function,
* where n is given by the byte that follows.
*/
++i;
int functionNumber = source.charAt(i);
if (nestedFunctions == null
|| functionNumber > nestedFunctions.length)
{
String message;
if (functionName != null && functionName.length() > 0) {
message = Context.getMessage2
("msg.no.function.ref.found.in",
new Integer(functionNumber), functionName);
} else {
message = Context.getMessage1
("msg.no.function.ref.found",
new Integer(functionNumber));
}
throw Context.reportRuntimeError(message);
}
decompile_r(nestedFunctions[functionNumber], version,
indent, false, false, srcData, result);
break;
}
case TokenStream.COMMA:
result.append(", ");
break;
case TokenStream.LC:
if (nextIs(source, length, i, TokenStream.EOL))
indent += OFFSET;
result.append('{');
break;
case TokenStream.RC:
/* don't print the closing RC if it closes the
* toplevel function and we're called from
* decompileFunctionBody.
*/
if (justbody && toplevel && i + 1 == length)
break;
if (nextIs(source, length, i, TokenStream.EOL))
indent -= OFFSET;
if (nextIs(source, length, i, TokenStream.WHILE)
|| nextIs(source, length, i, TokenStream.ELSE)) {
indent -= OFFSET;
result.append("} ");
}
else
result.append('}');
break;
case TokenStream.LP:
result.append('(');
break;
case TokenStream.RP:
if (nextIs(source, length, i, TokenStream.LC))
result.append(") ");
else
result.append(')');
break;
case TokenStream.LB:
result.append('[');
break;
case TokenStream.RB:
result.append(']');
break;
case TokenStream.EOL:
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 = source.charAt(i + 1);
if (nextToken == TokenStream.CASE
|| nextToken == TokenStream.DEFAULT)
less = SETBACK;
else if (nextToken == TokenStream.RC)
less = OFFSET;
/* elaborate check against label... skip past a
* following inlined NAME and look for a COLON.
* Depends on how NAME is encoded.
*/
else if (nextToken == TokenStream.NAME) {
int afterName = Parser.getSourceString(source, i + 2,
null);
if (source.charAt(afterName) == TokenStream.COLON)
less = OFFSET;
}
for (; less < indent; less++)
result.append(' ');
}
break;
case TokenStream.DOT:
result.append('.');
break;
case TokenStream.NEW:
result.append("new ");
break;
case TokenStream.DELPROP:
result.append("delete ");
break;
case TokenStream.IF:
result.append("if ");
break;
case TokenStream.ELSE:
result.append("else ");
break;
case TokenStream.FOR:
result.append("for ");
break;
case TokenStream.IN:
result.append(" in ");
break;
case TokenStream.WITH:
result.append("with ");
break;
case TokenStream.WHILE:
result.append("while ");
break;
case TokenStream.DO:
result.append("do ");
break;
case TokenStream.TRY:
result.append("try ");
break;
case TokenStream.CATCH:
result.append("catch ");
break;
case TokenStream.FINALLY:
result.append("finally ");
break;
case TokenStream.THROW:
result.append("throw ");
break;
case TokenStream.SWITCH:
result.append("switch ");
break;
case TokenStream.BREAK:
if (nextIs(source, length, i, TokenStream.NAME))
result.append("break ");
else
result.append("break");
break;
case TokenStream.CONTINUE:
if (nextIs(source, length, i, TokenStream.NAME))
result.append("continue ");
else
result.append("continue");
break;
case TokenStream.CASE:
result.append("case ");
break;
case TokenStream.DEFAULT:
result.append("default");
break;
case TokenStream.RETURN:
if (nextIs(source, length, i, TokenStream.SEMI))
result.append("return");
else
result.append("return ");
break;
case TokenStream.VAR:
result.append("var ");
break;
case TokenStream.SEMI:
if (nextIs(source, length, i, TokenStream.EOL))
// statement termination
result.append(';');
else
// separators in FOR
result.append("; ");
break;
case TokenStream.ASSIGN:
++i;
switch(source.charAt(i)) {
case TokenStream.NOP:
result.append(" = ");
break;
case TokenStream.ADD:
result.append(" += ");
break;
case TokenStream.SUB:
result.append(" -= ");
break;
case TokenStream.MUL:
result.append(" *= ");
break;
case TokenStream.DIV:
result.append(" /= ");
break;
case TokenStream.MOD:
result.append(" %= ");
break;
case TokenStream.BITOR:
result.append(" |= ");
break;
case TokenStream.BITXOR:
result.append(" ^= ");
break;
case TokenStream.BITAND:
result.append(" &= ");
break;
case TokenStream.LSH:
result.append(" <<= ");
break;
case TokenStream.RSH:
result.append(" >>= ");
break;
case TokenStream.URSH:
result.append(" >>>= ");
break;
}
break;
case TokenStream.HOOK:
result.append(" ? ");
break;
case TokenStream.OBJLIT:
// pun OBJLIT 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 TokenStream.COLON:
if (nextIs(source, length, i, TokenStream.EOL))
// it's the end of a label
result.append(':');
else
// it's the middle part of a ternary
result.append(" : ");
break;
case TokenStream.OR:
result.append(" || ");
break;
case TokenStream.AND:
result.append(" && ");
break;
case TokenStream.BITOR:
result.append(" | ");
break;
case TokenStream.BITXOR:
result.append(" ^ ");
break;
case TokenStream.BITAND:
result.append(" & ");
break;
case TokenStream.EQOP:
++i;
switch(source.charAt(i)) {
case TokenStream.SHEQ:
/*
* Emulate the C engine; if we're under version
* 1.2, then the == operator behaves like the ===
* operator (and the source is generated by
* decompiling a === opcode), so print the ===
* operator as ==.
*/
result.append(version == Context.VERSION_1_2
? " == " : " === ");
break;
case TokenStream.SHNE:
result.append(version == Context.VERSION_1_2
? " != " : " !== ");
break;
case TokenStream.EQ:
result.append(" == ");
break;
case TokenStream.NE:
result.append(" != ");
break;
}
break;
case TokenStream.RELOP:
++i;
switch(source.charAt(i)) {
case TokenStream.LE:
result.append(" <= ");
break;
case TokenStream.LT:
result.append(" < ");
break;
case TokenStream.GE:
result.append(" >= ");
break;
case TokenStream.GT:
result.append(" > ");
break;
case TokenStream.INSTANCEOF:
result.append(" instanceof ");
break;
}
break;
case TokenStream.SHOP:
++i;
switch(source.charAt(i)) {
case TokenStream.LSH:
result.append(" << ");
break;
case TokenStream.RSH:
result.append(" >> ");
break;
case TokenStream.URSH:
result.append(" >>> ");
break;
}
break;
case TokenStream.UNARYOP:
++i;
switch(source.charAt(i)) {
case TokenStream.TYPEOF:
result.append("typeof ");
break;
case TokenStream.VOID:
result.append("void ");
break;
case TokenStream.NOT:
result.append('!');
break;
case TokenStream.BITNOT:
result.append('~');
break;
case TokenStream.ADD:
result.append('+');
break;
case TokenStream.SUB:
result.append('-');
break;
}
break;
case TokenStream.INC:
result.append("++");
break;
case TokenStream.DEC:
result.append("--");
break;
case TokenStream.ADD:
result.append(" + ");
break;
case TokenStream.SUB:
result.append(" - ");
break;
case TokenStream.MUL:
result.append(" * ");
break;
case TokenStream.DIV:
result.append(" / ");
break;
case TokenStream.MOD:
result.append(" % ");
break;
default:
// If we don't know how to decompile it, raise an exception.
throw new RuntimeException();
}
++i;
}
// add that trailing newline if it's an outermost function.
if (toplevel && !justbody)
result.append('\n');
}
private static boolean nextIs(String source, int length, int i, int token) {
return (i + 1 < length) ? source.charAt(i + 1) == token : false;
}
private static int getSourceString(String source, int offset,
Object[] result)
{
int length = source.charAt(offset);
++offset;
if ((0x8000 & length) != 0) {
length = ((0x7FFF & length) << 16) | source.charAt(offset);
++offset;
}
if (result != null) {
result[0] = source.substring(offset, offset + length);
}
return offset + length;
}
private static int getSourceNumber(String source, int offset,
Object[] result)
{
char type = source.charAt(offset);
++offset;
if (type == 'S') {
@ -1588,25 +2251,6 @@ class Parser {
return offset;
}
private void increaseSourceCapacity(int minimalCapacity) {
// Call this only when capacity increase is must
if (Context.check && minimalCapacity <= sourceBuffer.length)
Context.codeBug();
int newCapacity = sourceBuffer.length * 2;
if (newCapacity < minimalCapacity) {
newCapacity = minimalCapacity;
}
char[] tmp = new char[newCapacity];
System.arraycopy(sourceBuffer, 0, tmp, 0, sourceTop);
sourceBuffer = tmp;
}
private String sourceToString(int offset) {
if (Context.check && (offset < 0 || sourceTop < offset))
Context.codeBug();
return new String(sourceBuffer, offset, sourceTop - offset);
}
private int lastExprEndLine; // Hack to handle function expr termination.
private IRFactory nf;
private ErrorReporter er;
@ -1615,5 +2259,16 @@ class Parser {
private char[] sourceBuffer = new char[128];
private int sourceTop;
private int functionNumber;
// how much to indent
private final static int OFFSET = 4;
// less how much for case labels
private final static int SETBACK = 2;
// whether to do a debug print of the source information, when
// decompiling.
private static final boolean printSource = false;
}