001 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file 002 // for details. All rights reserved. Use of this source code is governed by a 003 // BSD-style license that can be found in the LICENSE file. 004 005 package com.google.dart.compiler.backend.js; 006 007 import com.google.dart.compiler.backend.js.ast.*; 008 import com.google.dart.compiler.backend.js.ast.JsVars.JsVar; 009 import com.google.dart.compiler.util.TextOutput; 010 import gnu.trove.THashSet; 011 012 import java.util.Iterator; 013 import java.util.List; 014 import java.util.Map; 015 import java.util.Set; 016 017 import static com.google.dart.compiler.backend.js.ast.JsNumberLiteral.JsDoubleLiteral; 018 import static com.google.dart.compiler.backend.js.ast.JsNumberLiteral.JsIntLiteral; 019 020 /** 021 * Produces text output from a JavaScript AST. 022 */ 023 public class JsToStringGenerationVisitor extends JsVisitor { 024 private static final char[] CHARS_BREAK = "break".toCharArray(); 025 private static final char[] CHARS_CASE = "case".toCharArray(); 026 private static final char[] CHARS_CATCH = "catch".toCharArray(); 027 private static final char[] CHARS_CONTINUE = "continue".toCharArray(); 028 private static final char[] CHARS_DEBUGGER = "debugger".toCharArray(); 029 private static final char[] CHARS_DEFAULT = "default".toCharArray(); 030 private static final char[] CHARS_DO = "do".toCharArray(); 031 private static final char[] CHARS_ELSE = "else".toCharArray(); 032 private static final char[] CHARS_FALSE = "false".toCharArray(); 033 private static final char[] CHARS_FINALLY = "finally".toCharArray(); 034 private static final char[] CHARS_FOR = "for".toCharArray(); 035 private static final char[] CHARS_FUNCTION = "function".toCharArray(); 036 private static final char[] CHARS_IF = "if".toCharArray(); 037 private static final char[] CHARS_IN = "in".toCharArray(); 038 private static final char[] CHARS_NEW = "new".toCharArray(); 039 private static final char[] CHARS_NULL = "null".toCharArray(); 040 private static final char[] CHARS_RETURN = "return".toCharArray(); 041 private static final char[] CHARS_SWITCH = "switch".toCharArray(); 042 private static final char[] CHARS_THIS = "this".toCharArray(); 043 private static final char[] CHARS_THROW = "throw".toCharArray(); 044 private static final char[] CHARS_TRUE = "true".toCharArray(); 045 private static final char[] CHARS_TRY = "try".toCharArray(); 046 private static final char[] CHARS_VAR = "var".toCharArray(); 047 private static final char[] CHARS_WHILE = "while".toCharArray(); 048 private static final char[] HEX_DIGITS = { 049 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; 050 051 /** 052 * How many lines of code to print inside of a JsBlock when printing terse. 053 */ 054 private static final int JSBLOCK_LINES_TO_PRINT = 3; 055 056 public static CharSequence javaScriptString(String value) { 057 return javaScriptString(value, false); 058 } 059 060 /** 061 * Generate JavaScript code that evaluates to the supplied string. Adapted 062 * from {@link org.mozilla.javascript.ScriptRuntime#escapeString(String)} 063 * . The difference is that we quote with either " or ' depending on 064 * which one is used less inside the string. 065 */ 066 @SuppressWarnings({"ConstantConditions", "UnnecessaryFullyQualifiedName", "JavadocReference"}) 067 public static CharSequence javaScriptString(CharSequence chars, boolean forceDoubleQuote) { 068 final int n = chars.length(); 069 int quoteCount = 0; 070 int aposCount = 0; 071 072 for (int i = 0; i < n; i++) { 073 switch (chars.charAt(i)) { 074 case '"': 075 ++quoteCount; 076 break; 077 case '\'': 078 ++aposCount; 079 break; 080 } 081 } 082 083 StringBuilder result = new StringBuilder(n + 16); 084 085 char quoteChar = (quoteCount < aposCount || forceDoubleQuote) ? '"' : '\''; 086 result.append(quoteChar); 087 088 for (int i = 0; i < n; i++) { 089 char c = chars.charAt(i); 090 091 if (' ' <= c && c <= '~' && c != quoteChar && c != '\\') { 092 // an ordinary print character (like C isprint()) 093 result.append(c); 094 continue; 095 } 096 097 int escape = -1; 098 switch (c) { 099 case '\b': 100 escape = 'b'; 101 break; 102 case '\f': 103 escape = 'f'; 104 break; 105 case '\n': 106 escape = 'n'; 107 break; 108 case '\r': 109 escape = 'r'; 110 break; 111 case '\t': 112 escape = 't'; 113 break; 114 case '"': 115 escape = '"'; 116 break; // only reach here if == quoteChar 117 case '\'': 118 escape = '\''; 119 break; // only reach here if == quoteChar 120 case '\\': 121 escape = '\\'; 122 break; 123 } 124 125 if (escape >= 0) { 126 // an \escaped sort of character 127 result.append('\\'); 128 result.append((char) escape); 129 } 130 else { 131 /* 132 * Emit characters from 0 to 31 that don't have a single character 133 * escape sequence in octal where possible. This saves one or two 134 * characters compared to the hexadecimal format '\xXX'. 135 * 136 * These short octal sequences may only be used at the end of the string 137 * or where the following character is a non-digit. Otherwise, the 138 * following character would be incorrectly interpreted as belonging to 139 * the sequence. 140 */ 141 if (c < ' ' && (i == n - 1 || chars.charAt(i + 1) < '0' || chars.charAt(i + 1) > '9')) { 142 result.append('\\'); 143 if (c > 0x7) { 144 result.append((char) ('0' + (0x7 & (c >> 3)))); 145 } 146 result.append((char) ('0' + (0x7 & c))); 147 } 148 else { 149 int hexSize; 150 if (c < 256) { 151 // 2-digit hex 152 result.append("\\x"); 153 hexSize = 2; 154 } 155 else { 156 // Unicode. 157 result.append("\\u"); 158 hexSize = 4; 159 } 160 // append hexadecimal form of ch left-padded with 0 161 for (int shift = (hexSize - 1) * 4; shift >= 0; shift -= 4) { 162 int digit = 0xf & (c >> shift); 163 result.append(HEX_DIGITS[digit]); 164 } 165 } 166 } 167 } 168 result.append(quoteChar); 169 escapeClosingTags(result); 170 return result; 171 } 172 173 /** 174 * Escapes any closing XML tags embedded in <code>str</code>, which could 175 * potentially cause a parse failure in a browser, for example, embedding a 176 * closing <code><script></code> tag. 177 * 178 * @param str an unescaped literal; May be null 179 */ 180 private static void escapeClosingTags(StringBuilder str) { 181 if (str == null) { 182 return; 183 } 184 185 int index = 0; 186 while ((index = str.indexOf("</", index)) != -1) { 187 str.insert(index + 1, '\\'); 188 } 189 } 190 191 protected boolean needSemi = true; 192 private boolean lineBreakAfterBlock = true; 193 194 /** 195 * "Global" blocks are either the global block of a fragment, or a block 196 * nested directly within some other global block. This definition matters 197 * because the statements designated by statementEnds and statementStarts are 198 * those that appear directly within these global blocks. 199 */ 200 private Set<JsBlock> globalBlocks = new THashSet<JsBlock>(); 201 protected final TextOutput p; 202 203 public JsToStringGenerationVisitor(TextOutput out) { 204 p = out; 205 } 206 207 @Override 208 public void visitArrayAccess(JsArrayAccess x) { 209 printPair(x, x.getArrayExpression()); 210 leftSquare(); 211 accept(x.getIndexExpression()); 212 rightSquare(); 213 } 214 215 @Override 216 public void visitArray(JsArrayLiteral x) { 217 leftSquare(); 218 printExpressions(x.getExpressions()); 219 rightSquare(); 220 } 221 222 private void printExpressions(List<JsExpression> expressions) { 223 boolean notFirst = false; 224 for (JsExpression expression : expressions) { 225 notFirst = sepCommaOptSpace(notFirst) && !(expression instanceof JsDocComment); 226 boolean isEnclosed = parenPushIfCommaExpression(expression); 227 accept(expression); 228 if (isEnclosed) { 229 rightParen(); 230 } 231 } 232 } 233 234 @Override 235 public void visitBinaryExpression(JsBinaryOperation binaryOperation) { 236 JsBinaryOperator operator = binaryOperation.getOperator(); 237 JsExpression arg1 = binaryOperation.getArg1(); 238 boolean isExpressionEnclosed = parenPush(binaryOperation, arg1, !operator.isLeftAssociative()); 239 240 accept(arg1); 241 if (operator.isKeyword()) { 242 _parenPopOrSpace(binaryOperation, arg1, !operator.isLeftAssociative()); 243 } 244 else if (operator != JsBinaryOperator.COMMA) { 245 if (isExpressionEnclosed) { 246 rightParen(); 247 } 248 spaceOpt(); 249 } 250 251 p.print(operator.getSymbol()); 252 253 JsExpression arg2 = binaryOperation.getArg2(); 254 boolean isParenOpened; 255 if (operator == JsBinaryOperator.COMMA) { 256 isParenOpened = false; 257 spaceOpt(); 258 } 259 else if (arg2 instanceof JsBinaryOperation && ((JsBinaryOperation) arg2).getOperator() == JsBinaryOperator.AND) { 260 spaceOpt(); 261 leftParen(); 262 isParenOpened = true; 263 } 264 else { 265 if (spaceCalc(operator, arg2)) { 266 isParenOpened = _parenPushOrSpace(binaryOperation, arg2, operator.isLeftAssociative()); 267 } 268 else { 269 spaceOpt(); 270 isParenOpened = parenPush(binaryOperation, arg2, operator.isLeftAssociative()); 271 } 272 } 273 accept(arg2); 274 if (isParenOpened) { 275 rightParen(); 276 } 277 } 278 279 @Override 280 public void visitBlock(JsBlock x) { 281 printJsBlock(x, true, true); 282 } 283 284 @Override 285 public void visitBoolean(JsLiteral.JsBooleanLiteral x) { 286 if (x.getValue()) { 287 p.print(CHARS_TRUE); 288 } 289 else { 290 p.print(CHARS_FALSE); 291 } 292 } 293 294 @Override 295 public void visitBreak(JsBreak x) { 296 p.print(CHARS_BREAK); 297 continueOrBreakLabel(x); 298 } 299 300 @Override 301 public void visitContinue(JsContinue x) { 302 p.print(CHARS_CONTINUE); 303 continueOrBreakLabel(x); 304 } 305 306 private void continueOrBreakLabel(JsContinue x) { 307 String label = x.getLabel(); 308 if (label != null) { 309 space(); 310 p.print(label); 311 } 312 } 313 314 @Override 315 public void visitCase(JsCase x) { 316 p.print(CHARS_CASE); 317 space(); 318 accept(x.getCaseExpression()); 319 _colon(); 320 newlineOpt(); 321 322 printSwitchMemberStatements(x); 323 } 324 325 private void printSwitchMemberStatements(JsSwitchMember x) { 326 p.indentIn(); 327 for (JsStatement stmt : x.getStatements()) { 328 needSemi = true; 329 accept(stmt); 330 if (needSemi) { 331 semi(); 332 } 333 newlineOpt(); 334 } 335 p.indentOut(); 336 needSemi = false; 337 } 338 339 @Override 340 public void visitCatch(JsCatch x) { 341 spaceOpt(); 342 p.print(CHARS_CATCH); 343 spaceOpt(); 344 leftParen(); 345 nameDef(x.getParameter().getName()); 346 347 // Optional catch condition. 348 // 349 JsExpression catchCond = x.getCondition(); 350 if (catchCond != null) { 351 space(); 352 _if(); 353 space(); 354 accept(catchCond); 355 } 356 357 rightParen(); 358 spaceOpt(); 359 accept(x.getBody()); 360 } 361 362 @Override 363 public void visitConditional(JsConditional x) { 364 // Associativity: for the then and else branches, it is safe to insert 365 // another 366 // ternary expression, but if the test expression is a ternary, it should 367 // get parentheses around it. 368 printPair(x, x.getTestExpression(), true); 369 spaceOpt(); 370 p.print('?'); 371 spaceOpt(); 372 printPair(x, x.getThenExpression()); 373 spaceOpt(); 374 _colon(); 375 spaceOpt(); 376 printPair(x, x.getElseExpression()); 377 } 378 379 private void printPair(JsExpression parent, JsExpression expression, boolean wrongAssoc) { 380 boolean isNeedParen = parenCalc(parent, expression, wrongAssoc); 381 if (isNeedParen) { 382 leftParen(); 383 } 384 accept(expression); 385 if (isNeedParen) { 386 rightParen(); 387 } 388 } 389 390 private void printPair(JsExpression parent, JsExpression expression) { 391 printPair(parent, expression, false); 392 } 393 394 @Override 395 public void visitDebugger(JsDebugger x) { 396 p.print(CHARS_DEBUGGER); 397 } 398 399 @Override 400 public void visitDefault(JsDefault x) { 401 p.print(CHARS_DEFAULT); 402 _colon(); 403 404 printSwitchMemberStatements(x); 405 } 406 407 @Override 408 public void visitWhile(JsWhile x) { 409 _while(); 410 spaceOpt(); 411 leftParen(); 412 accept(x.getCondition()); 413 rightParen(); 414 nestedPush(x.getBody()); 415 accept(x.getBody()); 416 nestedPop(x.getBody()); 417 } 418 419 @Override 420 public void visitDoWhile(JsDoWhile x) { 421 p.print(CHARS_DO); 422 nestedPush(x.getBody()); 423 accept(x.getBody()); 424 nestedPop(x.getBody()); 425 if (needSemi) { 426 semi(); 427 newlineOpt(); 428 } 429 else { 430 spaceOpt(); 431 needSemi = true; 432 } 433 _while(); 434 spaceOpt(); 435 leftParen(); 436 accept(x.getCondition()); 437 rightParen(); 438 } 439 440 @Override 441 public void visitEmpty(JsEmpty x) { 442 } 443 444 @Override 445 public void visitExpressionStatement(JsExpressionStatement x) { 446 boolean surroundWithParentheses = JsFirstExpressionVisitor.exec(x); 447 if (surroundWithParentheses) { 448 leftParen(); 449 } 450 accept(x.getExpression()); 451 if (surroundWithParentheses) { 452 rightParen(); 453 } 454 } 455 456 @Override 457 public void visitFor(JsFor x) { 458 _for(); 459 spaceOpt(); 460 leftParen(); 461 462 // The init expressions or var decl. 463 // 464 if (x.getInitExpression() != null) { 465 accept(x.getInitExpression()); 466 } 467 else if (x.getInitVars() != null) { 468 accept(x.getInitVars()); 469 } 470 471 semi(); 472 473 // The loop test. 474 // 475 if (x.getCondition() != null) { 476 spaceOpt(); 477 accept(x.getCondition()); 478 } 479 480 semi(); 481 482 // The incr expression. 483 // 484 if (x.getIncrementExpression() != null) { 485 spaceOpt(); 486 accept(x.getIncrementExpression()); 487 } 488 489 rightParen(); 490 nestedPush(x.getBody()); 491 accept(x.getBody()); 492 nestedPop(x.getBody()); 493 } 494 495 @Override 496 public void visitForIn(JsForIn x) { 497 _for(); 498 spaceOpt(); 499 leftParen(); 500 501 if (x.getIterVarName() != null) { 502 var(); 503 space(); 504 nameDef(x.getIterVarName()); 505 506 if (x.getIterExpression() != null) { 507 spaceOpt(); 508 assignment(); 509 spaceOpt(); 510 accept(x.getIterExpression()); 511 } 512 } 513 else { 514 // Just a name ref. 515 // 516 accept(x.getIterExpression()); 517 } 518 519 space(); 520 p.print(CHARS_IN); 521 space(); 522 accept(x.getObjectExpression()); 523 524 rightParen(); 525 nestedPush(x.getBody()); 526 accept(x.getBody()); 527 nestedPop(x.getBody()); 528 } 529 530 @Override 531 public void visitFunction(JsFunction x) { 532 p.print(CHARS_FUNCTION); 533 space(); 534 if (x.getName() != null) { 535 nameOf(x); 536 } 537 538 leftParen(); 539 boolean notFirst = false; 540 for (Object element : x.getParameters()) { 541 JsParameter param = (JsParameter) element; 542 notFirst = sepCommaOptSpace(notFirst); 543 accept(param); 544 } 545 rightParen(); 546 space(); 547 548 lineBreakAfterBlock = false; 549 accept(x.getBody()); 550 needSemi = true; 551 } 552 553 @Override 554 public void visitIf(JsIf x) { 555 _if(); 556 spaceOpt(); 557 leftParen(); 558 accept(x.getIfExpression()); 559 rightParen(); 560 JsStatement thenStmt = x.getThenStatement(); 561 nestedPush(thenStmt); 562 accept(thenStmt); 563 nestedPop(thenStmt); 564 JsStatement elseStatement = x.getElseStatement(); 565 if (elseStatement != null) { 566 if (needSemi) { 567 semi(); 568 newlineOpt(); 569 } 570 else { 571 spaceOpt(); 572 needSemi = true; 573 } 574 p.print(CHARS_ELSE); 575 boolean elseIf = elseStatement instanceof JsIf; 576 if (!elseIf) { 577 nestedPush(elseStatement); 578 } 579 else { 580 space(); 581 } 582 accept(elseStatement); 583 if (!elseIf) { 584 nestedPop(elseStatement); 585 } 586 } 587 } 588 589 @Override 590 public void visitInvocation(JsInvocation invocation) { 591 printPair(invocation, invocation.getQualifier()); 592 593 leftParen(); 594 printExpressions(invocation.getArguments()); 595 rightParen(); 596 } 597 598 @Override 599 public void visitLabel(JsLabel x) { 600 nameOf(x); 601 _colon(); 602 spaceOpt(); 603 accept(x.getStatement()); 604 } 605 606 @Override 607 public void visitNameRef(JsNameRef nameRef) { 608 JsExpression qualifier = nameRef.getQualifier(); 609 if (qualifier != null) { 610 final boolean enclose; 611 if (qualifier instanceof JsLiteral.JsValueLiteral) { 612 // "42.foo" is not allowed, but "(42).foo" is. 613 enclose = qualifier instanceof JsNumberLiteral; 614 } 615 else { 616 enclose = parenCalc(nameRef, qualifier, false); 617 } 618 619 if (enclose) { 620 leftParen(); 621 } 622 accept(qualifier); 623 if (enclose) { 624 rightParen(); 625 } 626 p.print('.'); 627 } 628 629 p.maybeIndent(); 630 beforeNodePrinted(nameRef); 631 p.print(nameRef.getIdent()); 632 } 633 634 protected void beforeNodePrinted(JsNode node) { 635 } 636 637 @Override 638 public void visitNew(JsNew x) { 639 p.print(CHARS_NEW); 640 space(); 641 642 JsExpression constructorExpression = x.getConstructorExpression(); 643 boolean needsParens = JsConstructExpressionVisitor.exec(constructorExpression); 644 if (needsParens) { 645 leftParen(); 646 } 647 accept(constructorExpression); 648 if (needsParens) { 649 rightParen(); 650 } 651 652 leftParen(); 653 printExpressions(x.getArguments()); 654 rightParen(); 655 } 656 657 @Override 658 public void visitNull(JsNullLiteral x) { 659 p.print(CHARS_NULL); 660 } 661 662 @Override 663 public void visitInt(JsIntLiteral x) { 664 p.print(x.value); 665 } 666 667 @Override 668 public void visitDouble(JsDoubleLiteral x) { 669 p.print(x.value); 670 } 671 672 @Override 673 public void visitObjectLiteral(JsObjectLiteral objectLiteral) { 674 p.print('{'); 675 if (objectLiteral.isMultiline()) { 676 p.indentIn(); 677 } 678 679 boolean notFirst = false; 680 for (JsPropertyInitializer item : objectLiteral.getPropertyInitializers()) { 681 if (notFirst) { 682 p.print(','); 683 } 684 685 if (objectLiteral.isMultiline()) { 686 newlineOpt(); 687 } 688 else if (notFirst) { 689 spaceOpt(); 690 } 691 692 notFirst = true; 693 694 JsExpression labelExpr = item.getLabelExpr(); 695 // labels can be either string, integral, or decimal literals 696 if (labelExpr instanceof JsNameRef) { 697 p.print(((JsNameRef) labelExpr).getIdent()); 698 } 699 else if (labelExpr instanceof JsStringLiteral) { 700 p.print(((JsStringLiteral) labelExpr).getValue()); 701 } 702 else { 703 accept(labelExpr); 704 } 705 706 _colon(); 707 space(); 708 JsExpression valueExpr = item.getValueExpr(); 709 boolean wasEnclosed = parenPushIfCommaExpression(valueExpr); 710 accept(valueExpr); 711 if (wasEnclosed) { 712 rightParen(); 713 } 714 } 715 716 if (objectLiteral.isMultiline()) { 717 p.indentOut(); 718 newlineOpt(); 719 } 720 721 p.print('}'); 722 } 723 724 @Override 725 public void visitParameter(JsParameter x) { 726 nameOf(x); 727 } 728 729 @Override 730 public void visitPostfixOperation(JsPostfixOperation x) { 731 JsUnaryOperator op = x.getOperator(); 732 JsExpression arg = x.getArg(); 733 // unary operators always associate correctly (I think) 734 printPair(x, arg); 735 p.print(op.getSymbol()); 736 } 737 738 @Override 739 public void visitPrefixOperation(JsPrefixOperation x) { 740 JsUnaryOperator op = x.getOperator(); 741 p.print(op.getSymbol()); 742 JsExpression arg = x.getArg(); 743 if (spaceCalc(op, arg)) { 744 space(); 745 } 746 // unary operators always associate correctly (I think) 747 printPair(x, arg); 748 } 749 750 @Override 751 public void visitProgram(JsProgram x) { 752 p.print("<JsProgram>"); 753 } 754 755 @Override 756 public void visitProgramFragment(JsProgramFragment x) { 757 p.print("<JsProgramFragment>"); 758 } 759 760 @Override 761 public void visitRegExp(JsRegExp x) { 762 slash(); 763 p.print(x.getPattern()); 764 slash(); 765 String flags = x.getFlags(); 766 if (flags != null) { 767 p.print(flags); 768 } 769 } 770 771 @Override 772 public void visitReturn(JsReturn x) { 773 p.print(CHARS_RETURN); 774 JsExpression expr = x.getExpression(); 775 if (expr != null) { 776 space(); 777 accept(expr); 778 } 779 } 780 781 @Override 782 public void visitString(JsStringLiteral x) { 783 p.print(javaScriptString(x.getValue())); 784 } 785 786 @Override 787 public void visit(JsSwitch x) { 788 p.print(CHARS_SWITCH); 789 spaceOpt(); 790 leftParen(); 791 accept(x.getExpression()); 792 rightParen(); 793 spaceOpt(); 794 blockOpen(); 795 acceptList(x.getCases()); 796 blockClose(); 797 } 798 799 @Override 800 public void visitThis(JsLiteral.JsThisRef x) { 801 p.print(CHARS_THIS); 802 } 803 804 @Override 805 public void visitThrow(JsThrow x) { 806 p.print(CHARS_THROW); 807 space(); 808 accept(x.getExpression()); 809 } 810 811 @Override 812 public void visitTry(JsTry x) { 813 p.print(CHARS_TRY); 814 spaceOpt(); 815 accept(x.getTryBlock()); 816 817 acceptList(x.getCatches()); 818 819 JsBlock finallyBlock = x.getFinallyBlock(); 820 if (finallyBlock != null) { 821 p.print(CHARS_FINALLY); 822 spaceOpt(); 823 accept(finallyBlock); 824 } 825 } 826 827 @Override 828 public void visit(JsVar var) { 829 nameOf(var); 830 JsExpression initExpr = var.getInitExpression(); 831 if (initExpr != null) { 832 spaceOpt(); 833 assignment(); 834 spaceOpt(); 835 boolean isEnclosed = parenPushIfCommaExpression(initExpr); 836 accept(initExpr); 837 if (isEnclosed) { 838 rightParen(); 839 } 840 } 841 } 842 843 @Override 844 public void visitVars(JsVars vars) { 845 var(); 846 space(); 847 boolean sep = false; 848 for (JsVar var : vars) { 849 if (sep) { 850 if (vars.isMultiline()) { 851 newlineOpt(); 852 } 853 p.print(','); 854 spaceOpt(); 855 } 856 else { 857 sep = true; 858 } 859 860 accept(var); 861 } 862 } 863 864 @Override 865 public void visitDocComment(JsDocComment comment) { 866 boolean asSingleLine = comment.getTags().size() == 1; 867 if (!asSingleLine) { 868 newlineOpt(); 869 } 870 p.print("/**"); 871 if (asSingleLine) { 872 space(); 873 } 874 else { 875 p.newline(); 876 } 877 878 boolean notFirst = false; 879 for (Map.Entry<String, Object> entry : comment.getTags().entrySet()) { 880 if (notFirst) { 881 p.newline(); 882 p.print(' '); 883 p.print('*'); 884 } 885 else { 886 notFirst = true; 887 } 888 889 p.print('@'); 890 p.print(entry.getKey()); 891 Object value = entry.getValue(); 892 if (value != null) { 893 space(); 894 if (value instanceof CharSequence) { 895 p.print((CharSequence) value); 896 } 897 else { 898 visitNameRef((JsNameRef) value); 899 } 900 } 901 902 if (!asSingleLine) { 903 p.newline(); 904 } 905 } 906 907 if (asSingleLine) { 908 space(); 909 } 910 else { 911 newlineOpt(); 912 } 913 914 p.print('*'); 915 p.print('/'); 916 if (asSingleLine) { 917 spaceOpt(); 918 } 919 } 920 921 protected final void newlineOpt() { 922 if (!p.isCompact()) { 923 p.newline(); 924 } 925 } 926 927 protected void printJsBlock(JsBlock x, boolean truncate, boolean finalNewline) { 928 if (!lineBreakAfterBlock) { 929 finalNewline = false; 930 lineBreakAfterBlock = true; 931 } 932 933 boolean needBraces = !x.isGlobalBlock(); 934 if (needBraces) { 935 blockOpen(); 936 } 937 938 int count = 0; 939 Iterator<JsStatement> iterator = x.getStatements().iterator(); 940 while (iterator.hasNext()) { 941 boolean isGlobal = x.isGlobalBlock() || globalBlocks.contains(x); 942 943 if (truncate && count > JSBLOCK_LINES_TO_PRINT) { 944 p.print("[...]"); 945 newlineOpt(); 946 break; 947 } 948 JsStatement statement = iterator.next(); 949 if (statement instanceof JsEmpty) { 950 continue; 951 } 952 953 needSemi = true; 954 boolean stmtIsGlobalBlock = false; 955 if (isGlobal) { 956 if (statement instanceof JsBlock) { 957 // A block inside a global block is still considered global 958 stmtIsGlobalBlock = true; 959 globalBlocks.add((JsBlock) statement); 960 } 961 } 962 963 accept(statement); 964 if (stmtIsGlobalBlock) { 965 //noinspection SuspiciousMethodCalls 966 globalBlocks.remove(statement); 967 } 968 if (needSemi) { 969 /* 970 * Special treatment of function declarations: If they are the only item in a 971 * statement (i.e. not part of an assignment operation), just give them 972 * a newline instead of a semi. 973 */ 974 boolean functionStmt = 975 statement instanceof JsExpressionStatement && ((JsExpressionStatement) statement).getExpression() instanceof JsFunction; 976 /* 977 * Special treatment of the last statement in a block: only a few 978 * statements at the end of a block require semicolons. 979 */ 980 boolean lastStatement = !iterator.hasNext() && needBraces && !JsRequiresSemiVisitor.exec(statement); 981 if (functionStmt) { 982 if (lastStatement) { 983 newlineOpt(); 984 } 985 else { 986 p.newline(); 987 } 988 } 989 else { 990 if (lastStatement) { 991 p.printOpt(';'); 992 } 993 else { 994 semi(); 995 } 996 newlineOpt(); 997 } 998 } 999 ++count; 1000 } 1001 1002 if (needBraces) { 1003 // _blockClose() modified 1004 p.indentOut(); 1005 p.print('}'); 1006 if (finalNewline) { 1007 newlineOpt(); 1008 } 1009 } 1010 needSemi = false; 1011 } 1012 1013 private void assignment() { 1014 p.print('='); 1015 } 1016 1017 private void blockClose() { 1018 p.indentOut(); 1019 p.print('}'); 1020 newlineOpt(); 1021 } 1022 1023 private void blockOpen() { 1024 p.print('{'); 1025 p.indentIn(); 1026 newlineOpt(); 1027 } 1028 1029 private void _colon() { 1030 p.print(':'); 1031 } 1032 1033 private void _for() { 1034 p.print(CHARS_FOR); 1035 } 1036 1037 private void _if() { 1038 p.print(CHARS_IF); 1039 } 1040 1041 private void leftParen() { 1042 p.print('('); 1043 } 1044 1045 private void leftSquare() { 1046 p.print('['); 1047 } 1048 1049 private void nameDef(JsName name) { 1050 p.print(name.getIdent()); 1051 } 1052 1053 private void nameOf(HasName hasName) { 1054 nameDef(hasName.getName()); 1055 } 1056 1057 private boolean nestedPop(JsStatement statement) { 1058 boolean pop = !(statement instanceof JsBlock); 1059 if (pop) { 1060 p.indentOut(); 1061 } 1062 return pop; 1063 } 1064 1065 private boolean nestedPush(JsStatement statement) { 1066 boolean push = !(statement instanceof JsBlock); 1067 if (push) { 1068 newlineOpt(); 1069 p.indentIn(); 1070 } 1071 else { 1072 spaceOpt(); 1073 } 1074 return push; 1075 } 1076 1077 private static boolean parenCalc(JsExpression parent, JsExpression child, boolean wrongAssoc) { 1078 int parentPrec = JsPrecedenceVisitor.exec(parent); 1079 int childPrec = JsPrecedenceVisitor.exec(child); 1080 return parentPrec > childPrec || parentPrec == childPrec && wrongAssoc; 1081 } 1082 1083 private boolean _parenPopOrSpace(JsExpression parent, JsExpression child, boolean wrongAssoc) { 1084 boolean doPop = parenCalc(parent, child, wrongAssoc); 1085 if (doPop) { 1086 rightParen(); 1087 } 1088 else { 1089 space(); 1090 } 1091 return doPop; 1092 } 1093 1094 private boolean parenPush(JsExpression parent, JsExpression child, boolean wrongAssoc) { 1095 boolean doPush = parenCalc(parent, child, wrongAssoc); 1096 if (doPush) { 1097 leftParen(); 1098 } 1099 return doPush; 1100 } 1101 1102 private boolean parenPushIfCommaExpression(JsExpression x) { 1103 boolean doPush = x instanceof JsBinaryOperation && ((JsBinaryOperation) x).getOperator() == JsBinaryOperator.COMMA; 1104 if (doPush) { 1105 leftParen(); 1106 } 1107 return doPush; 1108 } 1109 1110 private boolean _parenPushOrSpace(JsExpression parent, JsExpression child, boolean wrongAssoc) { 1111 boolean doPush = parenCalc(parent, child, wrongAssoc); 1112 if (doPush) { 1113 leftParen(); 1114 } 1115 else { 1116 space(); 1117 } 1118 return doPush; 1119 } 1120 1121 private void rightParen() { 1122 p.print(')'); 1123 } 1124 1125 private void rightSquare() { 1126 p.print(']'); 1127 } 1128 1129 private void semi() { 1130 p.print(';'); 1131 } 1132 1133 private boolean sepCommaOptSpace(boolean sep) { 1134 if (sep) { 1135 p.print(','); 1136 spaceOpt(); 1137 } 1138 return true; 1139 } 1140 1141 private void slash() { 1142 p.print('/'); 1143 } 1144 1145 private void space() { 1146 p.print(' '); 1147 } 1148 1149 /** 1150 * Decide whether, if <code>op</code> is printed followed by <code>arg</code>, 1151 * there needs to be a space between the operator and expression. 1152 * 1153 * @return <code>true</code> if a space needs to be printed 1154 */ 1155 private static boolean spaceCalc(JsOperator op, JsExpression arg) { 1156 if (op.isKeyword()) { 1157 return true; 1158 } 1159 if (arg instanceof JsBinaryOperation) { 1160 JsBinaryOperation binary = (JsBinaryOperation) arg; 1161 /* 1162 * If the binary operation has a higher precedence than op, then it won't 1163 * be parenthesized, so check the first argument of the binary operation. 1164 */ 1165 return binary.getOperator().getPrecedence() > op.getPrecedence() && spaceCalc(op, binary.getArg1()); 1166 } 1167 if (arg instanceof JsPrefixOperation) { 1168 JsOperator op2 = ((JsPrefixOperation) arg).getOperator(); 1169 return (op == JsBinaryOperator.SUB || op == JsUnaryOperator.NEG) 1170 && (op2 == JsUnaryOperator.DEC || op2 == JsUnaryOperator.NEG) 1171 || (op == JsBinaryOperator.ADD && op2 == JsUnaryOperator.INC); 1172 } 1173 if (arg instanceof JsNumberLiteral && (op == JsBinaryOperator.SUB || op == JsUnaryOperator.NEG)) { 1174 if (arg instanceof JsIntLiteral) { 1175 return ((JsIntLiteral) arg).value < 0; 1176 } 1177 else { 1178 assert arg instanceof JsDoubleLiteral; 1179 //noinspection CastConflictsWithInstanceof 1180 return ((JsDoubleLiteral) arg).value < 0; 1181 } 1182 } 1183 return false; 1184 } 1185 1186 private void spaceOpt() { 1187 p.printOpt(' '); 1188 } 1189 1190 private void var() { 1191 p.print(CHARS_VAR); 1192 } 1193 1194 private void _while() { 1195 p.print(CHARS_WHILE); 1196 } 1197 }