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