nashorn/samples/prettyprinter.js
author iris
Sun, 25 Oct 2015 19:16:39 -0700
changeset 34009 b967d9c21d46
parent 29541 efc10427bddc
child 39662 e2b36a3779b9
permissions -rw-r--r--
Merge

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