/* * 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);