/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Oracle nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* This script is a AST pretty printer for ECMAScript. It uses
* Nashorn parser API to parser given script and uses tree visitor
* to pretty print the AST to stdout as a script string.
*/
var File = Java.type("java.io.File");
var file = arguments.length == 0? new File(__FILE__) : new File(arguments[0]);
if (! file.isFile()) {
print(arguments[0] + " is not a file");
exit(1);
}
// Java classes used
var ArrayAccess = Java.type("jdk.nashorn.api.tree.ArrayAccessTree");
var Block = Java.type("jdk.nashorn.api.tree.BlockTree");
var FunctionDeclaration = Java.type("jdk.nashorn.api.tree.FunctionDeclarationTree");
var FunctionExpression = Java.type("jdk.nashorn.api.tree.FunctionExpressionTree");
var Identifier = Java.type("jdk.nashorn.api.tree.IdentifierTree");
var Kind = Java.type("jdk.nashorn.api.tree.Tree.Kind");
var MemberSelect = Java.type("jdk.nashorn.api.tree.MemberSelectTree");
var ObjectLiteral = Java.type("jdk.nashorn.api.tree.ObjectLiteralTree");
var Parser = Java.type("jdk.nashorn.api.tree.Parser");
var SimpleTreeVisitor = Java.type("jdk.nashorn.api.tree.SimpleTreeVisitorES5_1");
var System = Java.type("java.lang.System");
// make a nashorn parser
var parser = Parser.create("-scripting", "--const-as-var");
// symbols for nashorn operators
var operatorSymbols = {
POSTFIX_INCREMENT: "++",
POSTFIX_DECREMENT: "--",
PREFIX_INCREMENT: "++",
PREFIX_DECREMENT: "--",
UNARY_PLUS: "+",
UNARY_MINUS: "-",
BITWISE_COMPLEMENT: "~",
LOGICAL_COMPLEMENT: "!",
DELETE: "delete ",
TYPEOF: "typeof ",
VOID: "void ",
COMMA: ",",
MULTIPLY: "*",
DIVIDE: "/",
REMINDER: "%",
PLUS: "+",
MINUS: "-",
LEFT_SHIFT: "<<",
RIGHT_SHIFT: ">>",
UNSIGNED_RIGHT_SHIFT: ">>>",
LESS_THAN: "<",
GREATER_THAN: ">",
LESS_THAN_EQUAL: "<=",
GREATER_THAN_EQUAL: ">=",
IN: "in",
EQUAL_TO: "==",
NOT_EQUAL_TO: "!=",
STRICT_EQUAL_TO: "===",
STRICT_NOT_EQUAL_TO: "!==",
AND: "&",
XOR: "^",
OR: "|",
CONDITIONAL_AND: "&&",
CONDITIONAL_OR: "||",
MULTIPLY_ASSIGNMENT: "*=",
DIVIDE_ASSIGNMENT: "/=",
REMINDER_ASSIGNMENT: "%=",
PLUS_ASSIGNMENT: "+=",
MINUS_ASSIGNMENT: "-=",
LEFT_SHIFT_ASSIGNMENT: "<<=",
RIGHT_SHIFT_ASSIGNMENT: ">>=",
UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: ">>>=",
AND_ASSIGNMENT: "&=",
XOR_ASSIGNMENT: "^=",
OR_ASSIGNMENT: "|="
};
function operatorOf(kind) {
var name = kind.name();
if (name in operatorSymbols) {
return operatorSymbols[name];
}
throw "invalid operator: " + name;
}
var gprint = print;
function prettyPrint(file) {
var ast = parser.parse(file, gprint);
if (!ast) {
// failed to parse. don't print anything!
return;
}
// AST visitor
var visitor;
// current indent level
var indentLevel = 0;
var out = System.out;
function print(obj) {
out.print(String(obj));
}
function println(obj) {
obj? out.println(String(obj)) : out.println();
}
// semicolon and end-of-line
function eol() {
println(";");
}
// print indentation - 4 spaces per level
function indent() {
for (var i = 0; i < indentLevel; i++) {
// 4 spaces per indent level
print(" ");
}
}
// escape string literals
function escapeString(str) {
// FIXME: incomplete, revisit again!
return str.replace(/[\\"']/g, '\\$&')
}
// print a single statement (could be a block too)
function printStatement(stat, extra, end) {
if (stat instanceof Block) {
println(" {");
printStatements(stat.statements, extra);
indent();
print('}');
typeof end != "undefined"? print(end) : println();
} else {
println();
indentLevel++;
try {
stat.accept(visitor, extra);
} finally {
indentLevel--;
}
}
}
// print a statement list
function printStatements(stats, extra) {
indentLevel++;
try {
for each (var stat in stats) {
stat.accept(visitor, extra);
}
} finally {
indentLevel--;
}
}
// function arguments, array literal elements.
function printCommaList(args, extra) {
var len = args.length;
for (var i = 0; i < len; i++) {
args[i].accept(visitor, extra);
if (i != len - 1) {
print(", ");
}
}
}
// print function declarations and expressions
function printFunction(func, extra, end) {
// extra lines around function declarations for clarity
var funcDecl = (func instanceof FunctionDeclaration);
if (funcDecl) {
println();
indent();
}
print("function ");
if (func.name) {
print(func.name.name);
}
printFunctionBody(func, extra, end);
if (funcDecl) {
println();
}
}
// print function declaration/expression body
function printFunctionBody(func, extra, end) {
print('(');
var params = func.parameters;
if (params) {
printCommaList(params);
}
print(')');
printStatement(func.body, extra, end);
}
// print object literal property
function printProperty(node, extra, comma) {
var key = node.key;
var val = node.value;
var getter = node.getter;
var setter = node.setter;
if (getter) {
print("get ");
} else if (setter) {
print("set ");
}
if (typeof key == "string") {
print(key);
} else {
key.accept(visitor, extra);
}
if (val) {
print(": ");
if (val instanceof FunctionExpression) {
printFunction(val, extra, comma? ',' : undefined);
} else {
val.accept(visitor, extra);
if (comma) print(',');
}
} else if (getter) {
printFunctionBody(getter, extra, comma? ',' : undefined);
} else if (setter) {
printFunctionBody(setter, extra, comma? ',' : undefined);
}
}
ast.accept(visitor = new (Java.extend(SimpleTreeVisitor)) {
visitAssignment: function(node, extra) {
node.variable.accept(visitor, extra);
print(" = ");
node.expression.accept(visitor, extra);
},
visitCompoundAssignment: function(node, extra) {
node.variable.accept(visitor, extra);
print(' ' + operatorOf(node.kind) + ' ');
node.expression.accept(visitor, extra);
},
visitBinary: function(node, extra) {
node.leftOperand.accept(visitor, extra);
print(' ' + operatorOf(node.kind) + ' ');
node.rightOperand.accept(visitor, extra);
},
visitBlock: function(node, extra) {
indent();
println('{');
printStatements(node.statements, extra);
indent();
println('}');
},
visitBreak: function(node, extra) {
indent();
print("break");
if (node.label) {
print(' ' + node.label);
}
eol();
},
visitCase: function(node, extra) {
var expr = node.expression;
indent();
if (expr) {
print("case ");
expr.accept(visitor, extra);
println(':');
} else {
println("default:");
}
printStatements(node.statements, extra);
},
visitCatch: function(node, extra) {
indent();
print("catch (" + node.parameter.name);
var cond = node.condition;
if (cond) {
print(" if ");
cond.accept(visitor, extra);
}
print(')');
printStatement(node.block);
},
visitConditionalExpression: function(node, extra) {
print('(');
node.condition.accept(visitor, extra);
print(" ? ");
node.trueExpression.accept(visitor, extra);
print(" : ");
node.falseExpression.accept(visitor, extra);
print(')');
},
visitContinue: function(node, extra) {
indent();
print("continue");
if (node.label) {
print(' ' + node.label);
}
eol();
},
visitDebugger: function(node, extra) {
indent();
print("debugger");
eol();
},
visitDoWhileLoop: function(node, extra) {
indent();
print("do");
printStatement(node.statement, extra);
indent();
print("while (");
node.condition.accept(visitor, extra);
print(')');
eol();
},
visitExpressionStatement: function(node, extra) {
indent();
var expr = node.expression;
var objLiteral = expr instanceof ObjectLiteral;
if (objLiteral) {
print('(');
}
expr.accept(visitor, extra);
if (objLiteral) {
print(')');
}
eol();
},
visitForLoop: function(node, extra) {
indent();
print("for (");
if (node.initializer) {
node.initializer.accept(visitor, extra);
}
print(';');
if (node.condition) {
node.condition.accept(visitor, extra);
}
print(';');
if (node.update) {
node.update.accept(visitor, extra);
}
print(')');
printStatement(node.statement);
},
visitForInLoop: function(node, extra) {
indent();
print("for ");
if (node.forEach) {
print("each ");
}
print('(');
node.variable.accept(visitor, extra);
print(" in ");
node.expression.accept(visitor, extra);
print(')');
printStatement(node.statement);
},
visitFunctionCall: function(node, extra) {
var func = node.functionSelect;
// We need parens around function selected
// in many non-simple cases. Eg. function
// expression created and called immediately.
// Such parens are not preserved in AST and so
// introduce here.
var simpleFunc =
(func instanceof ArrayAccess) ||
(func instanceof Identifier) ||
(func instanceof MemberSelect);
if (! simpleFunc) {
print('(');
}
func.accept(visitor, extra);
if (! simpleFunc) {
print(')');
}
print('(');
printCommaList(node.arguments, extra);
print(')');
},
visitFunctionDeclaration: function(node, extra) {
printFunction(node, extra);
},
visitFunctionExpression: function(node, extra) {
printFunction(node, extra);
},
visitIdentifier: function(node, extra) {
print(node.name);
},
visitIf: function(node, extra) {
indent();
print("if (");
node.condition.accept(visitor, extra);
print(')');
printStatement(node.thenStatement);
var el = node.elseStatement;
if (el) {
indent();
print("else");
printStatement(el);
}
},
visitArrayAccess: function(node, extra) {
node.expression.accept(visitor, extra);
print('[');
node.index.accept(visitor, extra);
print(']');
},
visitArrayLiteral: function(node, extra) {
print('[');
printCommaList(node.elements);
print(']');
},
visitLabeledStatement: function(node, extra) {
indent();
print(node.label);
print(':');
printStatement(node.statement);
},
visitLiteral: function(node, extra) {
var val = node.value;
if (typeof val == "string") {
print("'" + escapeString(val) + "'");
} else {
print(val);
}
},
visitParenthesized: function(node, extra) {
print('(');
node.expression.accept(visitor, extra);
print(')');
},
visitReturn: function(node, extra) {
indent();
print("return");
if (node.expression) {
print(' ');
node.expression.accept(visitor, extra);
}
eol();
},
visitMemberSelect: function(node, extra) {
node.expression.accept(visitor, extra);
print('.' + node.identifier);
},
visitNew: function(node, extra) {
print("new ");
node.constructorExpression.accept(visitor, extra);
},
visitObjectLiteral: function(node, extra) {
println('{');
indentLevel++;
try {
var props = node.properties;
var len = props.length;
for (var p = 0; p < len; p++) {
var last = (p == len - 1);
indent();
printProperty(props[p], extra, !last);
println();
}
} finally {
indentLevel--;
}
indent();
print('}');
},
visitRegExpLiteral: function(node, extra) {
print('/' + node.pattern + '/');
print(node.options);
},
visitEmptyStatement: function(node, extra) {
indent();
eol();
},
visitSwitch: function(node, extra) {
indent();
print("switch (");
node.expression.accept(visitor, extra);
println(") {");
indentLevel++;
try {
for each (var c in node.cases) {
c.accept(visitor, extra);
}
} finally {
indentLevel--;
}
indent();
println('}');
},
visitThrow: function(node, extra) {
indent();
print("throw ");
node.expression.accept(visitor, extra);
eol();
},
visitCompilationUnit: function(node, extra) {
for each (var stat in node.sourceElements) {
stat.accept(visitor, extra);
}
},
visitTry: function(node, extra) {
indent();
print("try");
printStatement(node.block);
var catches = node.catches;
for each (var c in catches) {
c.accept(visitor, extra);
}
var finallyBlock = node.finallyBlock;
if (finallyBlock) {
indent();
print("finally");
printStatement(finallyBlock);
}
},
visitInstanceOf: function(node, extra) {
node.expression.accept(visitor, extra);
print(" instanceof ");
node.type.accept(visitor, extra);
},
visitUnary: function(node, extra) {
var kind = node.kind;
var prefix = kind != Kind.POSTFIX_INCREMENT && kind != Kind.POSTFIX_DECREMENT;
if (prefix) {
print(operatorOf(kind));
}
node.expression.accept(visitor, extra);
if (!prefix) {
print(operatorOf(kind));
}
},
visitVariable: function(node, extra) {
indent();
print("var " + node.binding.name);
var init = node.initializer;
if (init) {
print(" = ");
if (init instanceof FunctionExpression) {
printFunction(init, extra, "");
} else {
init.accept(visitor, extra);
}
}
eol();
},
visitWhileLoop: function(node, extra) {
indent();
print("while (");
node.condition.accept(visitor, extra);
print(')');
printStatement(node.statement);
},
visitWith: function(node, extra) {
indent();
print("with (");
node.scope.accept(visitor, extra);
print(')');
printStatement(node.statement);
}
}, null);
}
prettyPrint(file);