1 /*
   2  * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
   3  *
   4  * Redistribution and use in source and binary forms, with or without
   5  * modification, are permitted provided that the following conditions
   6  * are met:
   7  *
   8  *   - Redistributions of source code must retain the above copyright
   9  *     notice, this list of conditions and the following disclaimer.
  10  *
  11  *   - Redistributions in binary form must reproduce the above copyright
  12  *     notice, this list of conditions and the following disclaimer in the
  13  *     documentation and/or other materials provided with the distribution.
  14  *
  15  *   - Neither the name of Oracle nor the names of its
  16  *     contributors may be used to endorse or promote products derived
  17  *     from this software without specific prior written permission.
  18  *
  19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  20  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  21  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  23  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  24  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  25  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  26  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  27  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  28  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  29  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30  */
  31 
  32 /**
  33  * This script is a AST pretty printer for ECMAScript. It uses
  34  * Nashorn parser API to parser given script and uses tree visitor
  35  * to pretty print the AST to stdout as a script string.
  36  */
  37 
  38 var File = Java.type("java.io.File");
  39 var file = arguments.length == 0? new File(__FILE__) : new File(arguments[0]);
  40 if (! file.isFile()) {
  41     print(arguments[0] + " is not a file");
  42     exit(1);
  43 }
  44 
  45 // Java classes used
  46 var ArrayAccess = Java.type("jdk.nashorn.api.tree.ArrayAccessTree");
  47 var Block = Java.type("jdk.nashorn.api.tree.BlockTree");
  48 var FunctionDeclaration = Java.type("jdk.nashorn.api.tree.FunctionDeclarationTree");
  49 var FunctionExpression = Java.type("jdk.nashorn.api.tree.FunctionExpressionTree");
  50 var Identifier = Java.type("jdk.nashorn.api.tree.IdentifierTree");
  51 var Kind = Java.type("jdk.nashorn.api.tree.Tree.Kind");
  52 var MemberSelect = Java.type("jdk.nashorn.api.tree.MemberSelectTree");
  53 var ObjectLiteral = Java.type("jdk.nashorn.api.tree.ObjectLiteralTree");
  54 var Parser = Java.type("jdk.nashorn.api.tree.Parser");
  55 var SimpleTreeVisitor = Java.type("jdk.nashorn.api.tree.SimpleTreeVisitorES5_1");
  56 var System = Java.type("java.lang.System");
  57 
  58 // make a nashorn parser
  59 var parser = Parser.create("-scripting", "--const-as-var");
  60 
  61 // symbols for nashorn operators
  62 var operatorSymbols = {
  63     POSTFIX_INCREMENT: "++", 
  64     POSTFIX_DECREMENT: "--",
  65     PREFIX_INCREMENT: "++", 
  66     PREFIX_DECREMENT: "--",
  67     UNARY_PLUS: "+",
  68     UNARY_MINUS: "-",
  69     BITWISE_COMPLEMENT: "~",
  70     LOGICAL_COMPLEMENT: "!",
  71     DELETE: "delete ",
  72     TYPEOF: "typeof ",
  73     VOID: "void ", 
  74     COMMA: ",",
  75     MULTIPLY: "*", 
  76     DIVIDE: "/", 
  77     REMINDER: "%", 
  78     PLUS: "+",
  79     MINUS: "-",
  80     LEFT_SHIFT: "<<",
  81     RIGHT_SHIFT: ">>",
  82     UNSIGNED_RIGHT_SHIFT: ">>>",
  83     LESS_THAN: "<",
  84     GREATER_THAN: ">",
  85     LESS_THAN_EQUAL: "<=",
  86     GREATER_THAN_EQUAL: ">=", 
  87     IN: "in", 
  88     EQUAL_TO: "==",
  89     NOT_EQUAL_TO: "!=",
  90     STRICT_EQUAL_TO: "===",
  91     STRICT_NOT_EQUAL_TO: "!==",
  92     AND: "&",
  93     XOR: "^",
  94     OR: "|", 
  95     CONDITIONAL_AND: "&&", 
  96     CONDITIONAL_OR: "||",
  97     MULTIPLY_ASSIGNMENT: "*=",
  98     DIVIDE_ASSIGNMENT: "/=",
  99     REMINDER_ASSIGNMENT: "%=",
 100     PLUS_ASSIGNMENT: "+=",
 101     MINUS_ASSIGNMENT: "-=",
 102     LEFT_SHIFT_ASSIGNMENT: "<<=",
 103     RIGHT_SHIFT_ASSIGNMENT: ">>=",
 104     UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: ">>>=",
 105     AND_ASSIGNMENT: "&=",
 106     XOR_ASSIGNMENT: "^=",
 107     OR_ASSIGNMENT: "|="
 108 };
 109 
 110 function operatorOf(kind) {
 111      var name = kind.name();
 112      if (name in operatorSymbols) {
 113          return operatorSymbols[name];
 114      }
 115      throw "invalid operator: " + name;
 116 }
 117 
 118 var gprint = print;
 119 
 120 function prettyPrint(file) {
 121     var ast = parser.parse(file, gprint);
 122     if (!ast) {
 123         // failed to parse. don't print anything!
 124         return;
 125     }
 126 
 127     // AST visitor
 128     var visitor;
 129     // current indent level
 130     var indentLevel = 0;
 131     var out = System.out;
 132 
 133     function print(obj) {
 134         out.print(String(obj));
 135     }
 136 
 137     function println(obj) {
 138         obj?  out.println(String(obj)) : out.println();
 139     }
 140 
 141     // semicolon and end-of-line
 142     function eol() {
 143         println(";");
 144     }
 145 
 146     // print indentation - 4 spaces per level
 147     function indent() {
 148         for (var i = 0; i < indentLevel; i++) {
 149             // 4 spaces per indent level
 150             print("    ");
 151         }
 152     }
 153 
 154     // escape string literals
 155     function escapeString(str) {
 156         // FIXME: incomplete, revisit again!
 157         return str.replace(/[\\"']/g, '\\$&')
 158     }
 159 
 160     // print a single statement (could be a block too)
 161     function printStatement(stat, extra, end) {
 162         if (stat instanceof Block) {
 163             println(" {");
 164             printStatements(stat.statements, extra);
 165             indent();
 166             print('}');
 167             typeof end != "undefined"? print(end) : println();
 168         } else {
 169             println();
 170             indentLevel++;
 171             try {
 172                 stat.accept(visitor, extra);
 173             } finally {
 174                 indentLevel--;
 175             }
 176         }
 177     }
 178 
 179     // print a statement list
 180     function printStatements(stats, extra) {
 181         indentLevel++;
 182         try {
 183             for each (var stat in stats) {
 184                 stat.accept(visitor, extra);
 185             }
 186         } finally {
 187             indentLevel--;
 188         }
 189     }
 190 
 191     // function arguments, array literal elements.
 192     function printCommaList(args, extra) {
 193         var len = args.length;
 194         for (var i = 0; i < len; i++) {
 195             args[i].accept(visitor, extra);
 196             if (i != len - 1) {
 197                 print(", ");
 198             }
 199         }
 200     }
 201 
 202     // print function declarations and expressions
 203     function printFunction(func, extra, end) {
 204         // extra lines around function declarations for clarity
 205         var funcDecl = (func instanceof FunctionDeclaration);
 206         if (funcDecl) {
 207             println();
 208             indent();
 209         }
 210         print("function ");
 211         if (func.name) {
 212             print(func.name.name);
 213         }
 214         printFunctionBody(func, extra, end);
 215         if (funcDecl) {
 216             println();
 217         }
 218     }
 219 
 220     // print function declaration/expression body
 221     function printFunctionBody(func, extra, end) {
 222         print('(');
 223         var params = func.parameters;
 224         if (params) {
 225             printCommaList(params);
 226         }
 227         print(')');
 228         printStatement(func.body, extra, end);
 229     }
 230 
 231     // print object literal property
 232     function printProperty(node, extra, comma) {
 233         var key = node.key;
 234         var val = node.value;
 235         var getter = node.getter;
 236         var setter = node.setter;
 237 
 238         if (getter) {
 239             print("get ");
 240         } else if (setter) {
 241             print("set ");
 242         }
 243 
 244         if (typeof key == "string") {
 245             print(key);
 246         } else {
 247             key.accept(visitor, extra);
 248         }
 249 
 250         if (val) {
 251             print(": ");
 252             if (val instanceof FunctionExpression) {
 253                 printFunction(val, extra, comma? ',' : undefined);
 254             } else {
 255                 val.accept(visitor, extra);
 256                 if (comma) print(',');
 257             }
 258         } else if (getter) {
 259             printFunctionBody(getter, extra, comma? ',' : undefined);
 260         } else if (setter) {
 261             printFunctionBody(setter, extra, comma? ',' : undefined);
 262         }
 263     }
 264 
 265 
 266     ast.accept(visitor = new (Java.extend(SimpleTreeVisitor)) {
 267          visitAssignment: function(node, extra) {
 268              node.variable.accept(visitor, extra);
 269              print(" = ");
 270              node.expression.accept(visitor, extra);
 271          },
 272 
 273          visitCompoundAssignment: function(node, extra) {
 274              node.variable.accept(visitor, extra);
 275              print(' ' + operatorOf(node.kind) + ' ');
 276              node.expression.accept(visitor, extra);
 277          },
 278 
 279          visitBinary: function(node, extra) {
 280              node.leftOperand.accept(visitor, extra);
 281              print(' ' + operatorOf(node.kind) + ' ');
 282              node.rightOperand.accept(visitor, extra);
 283          },
 284 
 285          visitBlock: function(node, extra) {
 286              indent();
 287              println('{');
 288              printStatements(node.statements, extra);
 289              indent();
 290              println('}');
 291          },
 292 
 293          visitBreak: function(node, extra) {
 294              indent();
 295              print("break");
 296              if (node.label) {
 297                  print(' ' + node.label);
 298              }
 299              eol();
 300          },
 301 
 302          visitCase: function(node, extra) {
 303              var expr = node.expression;
 304              indent();
 305              if (expr) {
 306                  print("case ");
 307                  expr.accept(visitor, extra);
 308                  println(':');
 309              } else {
 310                  println("default:");
 311              }
 312 
 313              printStatements(node.statements, extra);
 314          },
 315 
 316          visitCatch: function(node, extra) {
 317              indent();
 318              print("catch (" + node.parameter.name);
 319              var cond = node.condition;
 320              if (cond) {
 321                  print(" if ");
 322                  cond.accept(visitor, extra);
 323              }
 324              print(')');
 325              printStatement(node.block);
 326          },
 327 
 328          visitConditionalExpression: function(node, extra) {
 329              print('(');
 330              node.condition.accept(visitor, extra);
 331              print(" ? ");
 332              node.trueExpression.accept(visitor, extra);
 333              print(" : ");
 334              node.falseExpression.accept(visitor, extra);
 335              print(')');
 336          },
 337 
 338          visitContinue: function(node, extra) {
 339              indent();
 340              print("continue");
 341              if (node.label) {
 342                  print(' ' + node.label);
 343              }
 344              eol();
 345          },
 346 
 347          visitDebugger: function(node, extra) {
 348              indent();
 349              print("debugger");
 350              eol();
 351          },
 352 
 353          visitDoWhileLoop: function(node, extra) {
 354              indent();
 355              print("do");
 356              printStatement(node.statement, extra);
 357              indent();
 358              print("while (");
 359              node.condition.accept(visitor, extra);
 360              print(')');
 361              eol();
 362          },
 363 
 364          visitExpressionStatement: function(node, extra) {
 365              indent();
 366              var expr = node.expression;
 367              var objLiteral = expr instanceof ObjectLiteral;
 368              if (objLiteral) {
 369                  print('(');
 370              }
 371 
 372              expr.accept(visitor, extra);
 373              if (objLiteral) {
 374                  print(')');
 375              }
 376              eol();
 377          },
 378 
 379          visitForLoop: function(node, extra) {
 380              indent();
 381              print("for (");
 382              if (node.initializer) {
 383                 node.initializer.accept(visitor, extra);
 384              }
 385 
 386              print(';');
 387              if (node.condition) {
 388                 node.condition.accept(visitor, extra);
 389              }
 390              print(';');
 391              if (node.update) {
 392                 node.update.accept(visitor, extra);
 393              }
 394              print(')');
 395              printStatement(node.statement);
 396          },
 397 
 398          visitForInLoop: function(node, extra) {
 399              indent();
 400              print("for ");
 401              if (node.forEach) {
 402                  print("each ");
 403              }
 404              print('(');
 405              node.variable.accept(visitor, extra);
 406              print(" in ");
 407              node.expression.accept(visitor, extra);
 408              print(')');
 409              printStatement(node.statement);
 410          },
 411 
 412          visitFunctionCall: function(node, extra) {
 413              var func = node.functionSelect;
 414              // We need parens around function selected
 415              // in many non-simple cases. Eg. function
 416              // expression created and called immediately.
 417              // Such parens are not preserved in AST and so
 418              // introduce here.
 419              var simpleFunc =
 420                  (func instanceof ArrayAccess) ||
 421                  (func instanceof Identifier) ||
 422                  (func instanceof MemberSelect);
 423              if (! simpleFunc) {
 424                  print('(');
 425              }
 426              func.accept(visitor, extra);
 427              if (! simpleFunc) {
 428                  print(')');
 429              }
 430              print('(');
 431              printCommaList(node.arguments, extra);
 432              print(')');
 433          },
 434 
 435          visitFunctionDeclaration: function(node, extra) {
 436              printFunction(node, extra);
 437          },
 438 
 439          visitFunctionExpression: function(node, extra) {
 440              printFunction(node, extra);
 441          },
 442 
 443          visitIdentifier: function(node, extra) {
 444              print(node.name);
 445          },
 446 
 447          visitIf: function(node, extra) {
 448              indent();
 449              print("if (");
 450              node.condition.accept(visitor, extra);
 451              print(')');
 452              printStatement(node.thenStatement);
 453              var el = node.elseStatement;
 454              if (el) {
 455                  indent();
 456                  print("else");
 457                  printStatement(el);
 458              }
 459          },
 460 
 461          visitArrayAccess: function(node, extra) {
 462              node.expression.accept(visitor, extra);
 463              print('[');
 464              node.index.accept(visitor, extra);
 465              print(']');
 466          },
 467 
 468          visitArrayLiteral: function(node, extra) {
 469              print('[');
 470              printCommaList(node.elements);
 471              print(']');
 472          },
 473 
 474          visitLabeledStatement: function(node, extra) {
 475              indent();
 476              print(node.label);
 477              print(':');
 478              printStatement(node.statement);
 479          },
 480 
 481          visitLiteral: function(node, extra) {
 482              var val = node.value;
 483              if (typeof val == "string") {
 484                  print("'" + escapeString(val) + "'");
 485              } else {
 486                  print(val);
 487              }
 488          },
 489 
 490          visitParenthesized: function(node, extra) {
 491              print('(');
 492              node.expression.accept(visitor, extra);
 493              print(')');
 494          },
 495 
 496          visitReturn: function(node, extra) {
 497              indent();
 498              print("return");
 499              if (node.expression) {
 500                  print(' ');
 501                  node.expression.accept(visitor, extra);
 502              }
 503              eol();
 504          },
 505 
 506          visitMemberSelect: function(node, extra) {
 507              node.expression.accept(visitor, extra);
 508              print('.' + node.identifier);
 509          },
 510 
 511          visitNew: function(node, extra) {
 512              print("new ");
 513              node.constructorExpression.accept(visitor, extra);
 514          },
 515 
 516          visitObjectLiteral: function(node, extra) {
 517              println('{');
 518              indentLevel++;
 519              try {
 520                  var props = node.properties;
 521                  var len = props.length;
 522                  for (var p = 0; p < len; p++) {
 523                      var last = (p == len - 1);
 524                      indent();
 525                      printProperty(props[p], extra, !last);
 526                      println();
 527                  }
 528              } finally {
 529                  indentLevel--;
 530              }
 531              indent();
 532              print('}');
 533          },
 534 
 535          visitRegExpLiteral: function(node, extra) {
 536              print('/' + node.pattern + '/');
 537              print(node.options);
 538          },
 539 
 540          visitEmptyStatement: function(node, extra) {
 541              indent();
 542              eol();
 543          },
 544 
 545          visitSwitch: function(node, extra) {
 546              indent();
 547              print("switch (");
 548              node.expression.accept(visitor, extra);
 549              println(") {");
 550              indentLevel++;
 551              try {
 552                  for each (var c in node.cases) {
 553                      c.accept(visitor, extra);
 554                  }
 555              } finally {
 556                  indentLevel--;
 557              }
 558              indent();
 559              println('}');
 560          },
 561 
 562          visitThrow: function(node, extra) {
 563              indent();
 564              print("throw ");
 565              node.expression.accept(visitor, extra);
 566              eol();
 567          },
 568 
 569          visitCompilationUnit: function(node, extra) {
 570              for each (var stat in node.sourceElements) {
 571                  stat.accept(visitor, extra);
 572              }
 573          },
 574 
 575          visitTry: function(node, extra) {
 576              indent();
 577              print("try");
 578              printStatement(node.block);
 579              var catches = node.catches;
 580              for each (var c in catches) {
 581                  c.accept(visitor, extra);
 582              }
 583              var finallyBlock = node.finallyBlock;
 584              if (finallyBlock) {
 585                  indent();
 586                  print("finally");
 587                  printStatement(finallyBlock);
 588              }
 589          },
 590 
 591          visitInstanceOf: function(node, extra) {
 592              node.expression.accept(visitor, extra);
 593              print(" instanceof ");
 594              node.type.accept(visitor, extra);
 595          },
 596 
 597          visitUnary: function(node, extra) {
 598              var kind = node.kind;
 599              var prefix = kind != Kind.POSTFIX_INCREMENT && kind != Kind.POSTFIX_DECREMENT;
 600              if (prefix) {
 601                  print(operatorOf(kind));
 602              }
 603              node.expression.accept(visitor, extra);
 604              if (!prefix) {
 605                  print(operatorOf(kind));
 606              }
 607          },
 608 
 609          visitVariable: function(node, extra) {
 610              indent();
 611              print("var " + node.binding.name);
 612              var init = node.initializer;
 613              if (init) {
 614                  print(" = ");
 615                  if (init instanceof FunctionExpression) {
 616                      printFunction(init, extra, "");
 617                  } else {
 618                      init.accept(visitor, extra);
 619                  }
 620              }
 621              eol();
 622          },
 623 
 624          visitWhileLoop: function(node, extra) {
 625              indent();
 626              print("while (");
 627              node.condition.accept(visitor, extra);
 628              print(')');
 629              printStatement(node.statement);
 630          },
 631 
 632          visitWith: function(node, extra) {
 633              indent();
 634              print("with (");
 635              node.scope.accept(visitor, extra);
 636              print(')');
 637              printStatement(node.statement);
 638          }
 639     }, null);
 640 }
 641 
 642 prettyPrint(file);