1 /* 2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.nashorn.internal.ir.debug; 27 28 import static jdk.nashorn.internal.runtime.Source.sourceFor; 29 30 import java.util.ArrayList; 31 import java.util.List; 32 import jdk.nashorn.internal.ir.AccessNode; 33 import jdk.nashorn.internal.ir.BinaryNode; 34 import jdk.nashorn.internal.ir.Block; 35 import jdk.nashorn.internal.ir.BlockStatement; 36 import jdk.nashorn.internal.ir.BreakNode; 37 import jdk.nashorn.internal.ir.CallNode; 38 import jdk.nashorn.internal.ir.CaseNode; 39 import jdk.nashorn.internal.ir.CatchNode; 40 import jdk.nashorn.internal.ir.ContinueNode; 41 import jdk.nashorn.internal.ir.DebuggerNode; 42 import jdk.nashorn.internal.ir.EmptyNode; 43 import jdk.nashorn.internal.ir.Expression; 44 import jdk.nashorn.internal.ir.ExpressionStatement; 45 import jdk.nashorn.internal.ir.ForNode; 46 import jdk.nashorn.internal.ir.FunctionNode; 47 import jdk.nashorn.internal.ir.IdentNode; 48 import jdk.nashorn.internal.ir.IfNode; 49 import jdk.nashorn.internal.ir.IndexNode; 50 import jdk.nashorn.internal.ir.JoinPredecessorExpression; 51 import jdk.nashorn.internal.ir.LabelNode; 52 import jdk.nashorn.internal.ir.LiteralNode; 53 import jdk.nashorn.internal.ir.Node; 54 import jdk.nashorn.internal.ir.ObjectNode; 55 import jdk.nashorn.internal.ir.PropertyNode; 56 import jdk.nashorn.internal.ir.ReturnNode; 57 import jdk.nashorn.internal.ir.RuntimeNode; 58 import jdk.nashorn.internal.ir.SplitNode; 59 import jdk.nashorn.internal.ir.Statement; 60 import jdk.nashorn.internal.ir.SwitchNode; 61 import jdk.nashorn.internal.ir.TernaryNode; 62 import jdk.nashorn.internal.ir.ThrowNode; 63 import jdk.nashorn.internal.ir.TryNode; 64 import jdk.nashorn.internal.ir.UnaryNode; 65 import jdk.nashorn.internal.ir.VarNode; 66 import jdk.nashorn.internal.ir.WhileNode; 67 import jdk.nashorn.internal.ir.WithNode; 68 import jdk.nashorn.internal.ir.visitor.SimpleNodeVisitor; 69 import jdk.nashorn.internal.parser.JSONParser; 70 import jdk.nashorn.internal.parser.Lexer.RegexToken; 71 import jdk.nashorn.internal.parser.Parser; 72 import jdk.nashorn.internal.parser.TokenType; 73 import jdk.nashorn.internal.runtime.Context; 74 import jdk.nashorn.internal.runtime.ParserException; 75 import jdk.nashorn.internal.runtime.Source; 76 77 /** 78 * This IR writer produces a JSON string that represents AST as a JSON string. 79 */ 80 public final class JSONWriter extends SimpleNodeVisitor { 81 82 /** 83 * Returns AST as JSON compatible string. 84 * 85 * @param context context 86 * @param code code to be parsed 87 * @param name name of the code source (used for location) 88 * @param includeLoc tells whether to include location information for nodes or not 89 * @return JSON string representation of AST of the supplied code 90 */ parse(final Context context, final String code, final String name, final boolean includeLoc)91 public static String parse(final Context context, final String code, final String name, final boolean includeLoc) { 92 final Parser parser = new Parser(context.getEnv(), sourceFor(name, code), new Context.ThrowErrorManager(), context.getEnv()._strict, context.getLogger(Parser.class)); 93 final JSONWriter jsonWriter = new JSONWriter(includeLoc); 94 try { 95 final FunctionNode functionNode = parser.parse(); //symbol name is ":program", default 96 functionNode.accept(jsonWriter); 97 return jsonWriter.getString(); 98 } catch (final ParserException e) { 99 e.throwAsEcmaException(); 100 return null; 101 } 102 } 103 104 @Override enterJoinPredecessorExpression(final JoinPredecessorExpression joinPredecessorExpression)105 public boolean enterJoinPredecessorExpression(final JoinPredecessorExpression joinPredecessorExpression) { 106 final Expression expr = joinPredecessorExpression.getExpression(); 107 if(expr != null) { 108 expr.accept(this); 109 } else { 110 nullValue(); 111 } 112 return false; 113 } 114 115 @Override enterDefault(final Node node)116 protected boolean enterDefault(final Node node) { 117 objectStart(); 118 location(node); 119 120 return true; 121 } 122 leave()123 private boolean leave() { 124 objectEnd(); 125 return false; 126 } 127 128 @Override leaveDefault(final Node node)129 protected Node leaveDefault(final Node node) { 130 objectEnd(); 131 return null; 132 } 133 134 @Override enterAccessNode(final AccessNode accessNode)135 public boolean enterAccessNode(final AccessNode accessNode) { 136 enterDefault(accessNode); 137 138 type("MemberExpression"); 139 comma(); 140 141 property("object"); 142 accessNode.getBase().accept(this); 143 comma(); 144 145 property("property", accessNode.getProperty()); 146 comma(); 147 148 property("computed", false); 149 150 return leave(); 151 } 152 153 @Override enterBlock(final Block block)154 public boolean enterBlock(final Block block) { 155 enterDefault(block); 156 157 type("BlockStatement"); 158 comma(); 159 160 array("body", block.getStatements()); 161 162 return leave(); 163 } 164 165 @Override enterBinaryNode(final BinaryNode binaryNode)166 public boolean enterBinaryNode(final BinaryNode binaryNode) { 167 enterDefault(binaryNode); 168 169 final String name; 170 if (binaryNode.isAssignment()) { 171 name = "AssignmentExpression"; 172 } else if (binaryNode.isLogical()) { 173 name = "LogicalExpression"; 174 } else { 175 name = "BinaryExpression"; 176 } 177 178 type(name); 179 comma(); 180 181 property("operator", binaryNode.tokenType().getName()); 182 comma(); 183 184 property("left"); 185 binaryNode.lhs().accept(this); 186 comma(); 187 188 property("right"); 189 binaryNode.rhs().accept(this); 190 191 return leave(); 192 } 193 194 @Override enterBreakNode(final BreakNode breakNode)195 public boolean enterBreakNode(final BreakNode breakNode) { 196 enterDefault(breakNode); 197 198 type("BreakStatement"); 199 comma(); 200 201 final String label = breakNode.getLabelName(); 202 if(label != null) { 203 property("label", label); 204 } else { 205 property("label"); 206 nullValue(); 207 } 208 209 return leave(); 210 } 211 212 @Override enterCallNode(final CallNode callNode)213 public boolean enterCallNode(final CallNode callNode) { 214 enterDefault(callNode); 215 216 type("CallExpression"); 217 comma(); 218 219 property("callee"); 220 callNode.getFunction().accept(this); 221 comma(); 222 223 array("arguments", callNode.getArgs()); 224 225 return leave(); 226 } 227 228 @Override enterCaseNode(final CaseNode caseNode)229 public boolean enterCaseNode(final CaseNode caseNode) { 230 enterDefault(caseNode); 231 232 type("SwitchCase"); 233 comma(); 234 235 final Node test = caseNode.getTest(); 236 property("test"); 237 if (test != null) { 238 test.accept(this); 239 } else { 240 nullValue(); 241 } 242 comma(); 243 244 array("consequent", caseNode.getBody().getStatements()); 245 246 return leave(); 247 } 248 249 @Override enterCatchNode(final CatchNode catchNode)250 public boolean enterCatchNode(final CatchNode catchNode) { 251 enterDefault(catchNode); 252 253 type("CatchClause"); 254 comma(); 255 256 property("param"); 257 catchNode.getException().accept(this); 258 comma(); 259 260 final Node guard = catchNode.getExceptionCondition(); 261 if (guard != null) { 262 property("guard"); 263 guard.accept(this); 264 comma(); 265 } 266 267 property("body"); 268 catchNode.getBody().accept(this); 269 270 return leave(); 271 } 272 273 @Override enterContinueNode(final ContinueNode continueNode)274 public boolean enterContinueNode(final ContinueNode continueNode) { 275 enterDefault(continueNode); 276 277 type("ContinueStatement"); 278 comma(); 279 280 final String label = continueNode.getLabelName(); 281 if(label != null) { 282 property("label", label); 283 } else { 284 property("label"); 285 nullValue(); 286 } 287 288 return leave(); 289 } 290 291 @Override enterDebuggerNode(final DebuggerNode debuggerNode)292 public boolean enterDebuggerNode(final DebuggerNode debuggerNode) { 293 enterDefault(debuggerNode); 294 type("DebuggerStatement"); 295 return leave(); 296 } 297 298 @Override enterEmptyNode(final EmptyNode emptyNode)299 public boolean enterEmptyNode(final EmptyNode emptyNode) { 300 enterDefault(emptyNode); 301 302 type("EmptyStatement"); 303 304 return leave(); 305 } 306 307 @Override enterExpressionStatement(final ExpressionStatement expressionStatement)308 public boolean enterExpressionStatement(final ExpressionStatement expressionStatement) { 309 // handle debugger statement 310 final Node expression = expressionStatement.getExpression(); 311 if (expression instanceof RuntimeNode) { 312 assert false : "should not reach here: RuntimeNode"; 313 return false; 314 } 315 316 enterDefault(expressionStatement); 317 318 type("ExpressionStatement"); 319 comma(); 320 321 property("expression"); 322 expression.accept(this); 323 324 return leave(); 325 } 326 327 @Override enterBlockStatement(final BlockStatement blockStatement)328 public boolean enterBlockStatement(final BlockStatement blockStatement) { 329 if (blockStatement.isSynthetic()) { 330 final Block blk = blockStatement.getBlock(); 331 blk.getStatements().get(0).accept(this); 332 return false; 333 } 334 335 enterDefault(blockStatement); 336 337 type("BlockStatement"); 338 comma(); 339 340 array("body", blockStatement.getBlock().getStatements()); 341 return leave(); 342 } 343 344 @Override enterForNode(final ForNode forNode)345 public boolean enterForNode(final ForNode forNode) { 346 enterDefault(forNode); 347 348 if (forNode.isForIn() || (forNode.isForEach() && forNode.getInit() != null)) { 349 type("ForInStatement"); 350 comma(); 351 352 final Node init = forNode.getInit(); 353 assert init != null; 354 property("left"); 355 init.accept(this); 356 comma(); 357 358 final Node modify = forNode.getModify(); 359 assert modify != null; 360 property("right"); 361 modify.accept(this); 362 comma(); 363 364 property("body"); 365 forNode.getBody().accept(this); 366 comma(); 367 368 property("each", forNode.isForEach()); 369 } else { 370 type("ForStatement"); 371 comma(); 372 373 final Node init = forNode.getInit(); 374 property("init"); 375 if (init != null) { 376 init.accept(this); 377 } else { 378 nullValue(); 379 } 380 comma(); 381 382 final Node test = forNode.getTest(); 383 property("test"); 384 if (test != null) { 385 test.accept(this); 386 } else { 387 nullValue(); 388 } 389 comma(); 390 391 final Node update = forNode.getModify(); 392 property("update"); 393 if (update != null) { 394 update.accept(this); 395 } else { 396 nullValue(); 397 } 398 comma(); 399 400 property("body"); 401 forNode.getBody().accept(this); 402 } 403 404 return leave(); 405 } 406 407 @Override enterFunctionNode(final FunctionNode functionNode)408 public boolean enterFunctionNode(final FunctionNode functionNode) { 409 final boolean program = functionNode.isProgram(); 410 if (program) { 411 return emitProgram(functionNode); 412 } 413 414 enterDefault(functionNode); 415 final String name; 416 if (functionNode.isDeclared()) { 417 name = "FunctionDeclaration"; 418 } else { 419 name = "FunctionExpression"; 420 } 421 type(name); 422 comma(); 423 424 property("id"); 425 final FunctionNode.Kind kind = functionNode.getKind(); 426 if (functionNode.isAnonymous() || kind == FunctionNode.Kind.GETTER || kind == FunctionNode.Kind.SETTER) { 427 nullValue(); 428 } else { 429 functionNode.getIdent().accept(this); 430 } 431 comma(); 432 433 array("params", functionNode.getParameters()); 434 comma(); 435 436 arrayStart("defaults"); 437 arrayEnd(); 438 comma(); 439 440 property("rest"); 441 nullValue(); 442 comma(); 443 444 property("body"); 445 functionNode.getBody().accept(this); 446 comma(); 447 448 property("generator", false); 449 comma(); 450 451 property("expression", false); 452 453 return leave(); 454 } 455 emitProgram(final FunctionNode functionNode)456 private boolean emitProgram(final FunctionNode functionNode) { 457 enterDefault(functionNode); 458 type("Program"); 459 comma(); 460 461 // body consists of nested functions and statements 462 final List<Statement> stats = functionNode.getBody().getStatements(); 463 final int size = stats.size(); 464 int idx = 0; 465 arrayStart("body"); 466 467 for (final Node stat : stats) { 468 stat.accept(this); 469 if (idx != (size - 1)) { 470 comma(); 471 } 472 idx++; 473 } 474 arrayEnd(); 475 476 return leave(); 477 } 478 479 @Override enterIdentNode(final IdentNode identNode)480 public boolean enterIdentNode(final IdentNode identNode) { 481 enterDefault(identNode); 482 483 final String name = identNode.getName(); 484 if ("this".equals(name)) { 485 type("ThisExpression"); 486 } else { 487 type("Identifier"); 488 comma(); 489 property("name", identNode.getName()); 490 } 491 492 return leave(); 493 } 494 495 @Override enterIfNode(final IfNode ifNode)496 public boolean enterIfNode(final IfNode ifNode) { 497 enterDefault(ifNode); 498 499 type("IfStatement"); 500 comma(); 501 502 property("test"); 503 ifNode.getTest().accept(this); 504 comma(); 505 506 property("consequent"); 507 ifNode.getPass().accept(this); 508 final Node elsePart = ifNode.getFail(); 509 comma(); 510 511 property("alternate"); 512 if (elsePart != null) { 513 elsePart.accept(this); 514 } else { 515 nullValue(); 516 } 517 518 return leave(); 519 } 520 521 @Override enterIndexNode(final IndexNode indexNode)522 public boolean enterIndexNode(final IndexNode indexNode) { 523 enterDefault(indexNode); 524 525 type("MemberExpression"); 526 comma(); 527 528 property("object"); 529 indexNode.getBase().accept(this); 530 comma(); 531 532 property("property"); 533 indexNode.getIndex().accept(this); 534 comma(); 535 536 property("computed", true); 537 538 return leave(); 539 } 540 541 @Override enterLabelNode(final LabelNode labelNode)542 public boolean enterLabelNode(final LabelNode labelNode) { 543 enterDefault(labelNode); 544 545 type("LabeledStatement"); 546 comma(); 547 548 property("label", labelNode.getLabelName()); 549 comma(); 550 551 property("body"); 552 labelNode.getBody().accept(this); 553 554 return leave(); 555 } 556 557 @SuppressWarnings("rawtypes") 558 @Override enterLiteralNode(final LiteralNode literalNode)559 public boolean enterLiteralNode(final LiteralNode literalNode) { 560 enterDefault(literalNode); 561 562 if (literalNode instanceof LiteralNode.ArrayLiteralNode) { 563 type("ArrayExpression"); 564 comma(); 565 566 array("elements", ((LiteralNode.ArrayLiteralNode)literalNode).getElementExpressions()); 567 } else { 568 type("Literal"); 569 comma(); 570 571 property("value"); 572 final Object value = literalNode.getValue(); 573 if (value instanceof RegexToken) { 574 // encode RegExp literals as Strings of the form /.../<flags> 575 final RegexToken regex = (RegexToken)value; 576 final StringBuilder regexBuf = new StringBuilder(); 577 regexBuf.append('/'); 578 regexBuf.append(regex.getExpression()); 579 regexBuf.append('/'); 580 regexBuf.append(regex.getOptions()); 581 buf.append(quote(regexBuf.toString())); 582 } else { 583 final String str = literalNode.getString(); 584 // encode every String literal with prefix '$' so that script 585 // can differentiate b/w RegExps as Strings and Strings. 586 buf.append(literalNode.isString()? quote("$" + str) : str); 587 } 588 } 589 590 return leave(); 591 } 592 593 @Override enterObjectNode(final ObjectNode objectNode)594 public boolean enterObjectNode(final ObjectNode objectNode) { 595 enterDefault(objectNode); 596 597 type("ObjectExpression"); 598 comma(); 599 600 array("properties", objectNode.getElements()); 601 602 return leave(); 603 } 604 605 @Override enterPropertyNode(final PropertyNode propertyNode)606 public boolean enterPropertyNode(final PropertyNode propertyNode) { 607 final Node key = propertyNode.getKey(); 608 609 final Node value = propertyNode.getValue(); 610 if (value != null) { 611 objectStart(); 612 location(propertyNode); 613 614 property("key"); 615 key.accept(this); 616 comma(); 617 618 property("value"); 619 value.accept(this); 620 comma(); 621 622 property("kind", "init"); 623 624 objectEnd(); 625 } else { 626 // getter 627 final Node getter = propertyNode.getGetter(); 628 if (getter != null) { 629 objectStart(); 630 location(propertyNode); 631 632 property("key"); 633 key.accept(this); 634 comma(); 635 636 property("value"); 637 getter.accept(this); 638 comma(); 639 640 property("kind", "get"); 641 642 objectEnd(); 643 } 644 645 // setter 646 final Node setter = propertyNode.getSetter(); 647 if (setter != null) { 648 if (getter != null) { 649 comma(); 650 } 651 objectStart(); 652 location(propertyNode); 653 654 property("key"); 655 key.accept(this); 656 comma(); 657 658 property("value"); 659 setter.accept(this); 660 comma(); 661 662 property("kind", "set"); 663 664 objectEnd(); 665 } 666 } 667 668 return false; 669 } 670 671 @Override enterReturnNode(final ReturnNode returnNode)672 public boolean enterReturnNode(final ReturnNode returnNode) { 673 enterDefault(returnNode); 674 675 type("ReturnStatement"); 676 comma(); 677 678 final Node arg = returnNode.getExpression(); 679 property("argument"); 680 if (arg != null) { 681 arg.accept(this); 682 } else { 683 nullValue(); 684 } 685 686 return leave(); 687 } 688 689 @Override enterRuntimeNode(final RuntimeNode runtimeNode)690 public boolean enterRuntimeNode(final RuntimeNode runtimeNode) { 691 assert false : "should not reach here: RuntimeNode"; 692 return false; 693 } 694 695 @Override enterSplitNode(final SplitNode splitNode)696 public boolean enterSplitNode(final SplitNode splitNode) { 697 assert false : "should not reach here: SplitNode"; 698 return false; 699 } 700 701 @Override enterSwitchNode(final SwitchNode switchNode)702 public boolean enterSwitchNode(final SwitchNode switchNode) { 703 enterDefault(switchNode); 704 705 type("SwitchStatement"); 706 comma(); 707 708 property("discriminant"); 709 switchNode.getExpression().accept(this); 710 comma(); 711 712 array("cases", switchNode.getCases()); 713 714 return leave(); 715 } 716 717 @Override enterTernaryNode(final TernaryNode ternaryNode)718 public boolean enterTernaryNode(final TernaryNode ternaryNode) { 719 enterDefault(ternaryNode); 720 721 type("ConditionalExpression"); 722 comma(); 723 724 property("test"); 725 ternaryNode.getTest().accept(this); 726 comma(); 727 728 property("consequent"); 729 ternaryNode.getTrueExpression().accept(this); 730 comma(); 731 732 property("alternate"); 733 ternaryNode.getFalseExpression().accept(this); 734 735 return leave(); 736 } 737 738 @Override enterThrowNode(final ThrowNode throwNode)739 public boolean enterThrowNode(final ThrowNode throwNode) { 740 enterDefault(throwNode); 741 742 type("ThrowStatement"); 743 comma(); 744 745 property("argument"); 746 throwNode.getExpression().accept(this); 747 748 return leave(); 749 } 750 751 @Override enterTryNode(final TryNode tryNode)752 public boolean enterTryNode(final TryNode tryNode) { 753 enterDefault(tryNode); 754 755 type("TryStatement"); 756 comma(); 757 758 property("block"); 759 tryNode.getBody().accept(this); 760 comma(); 761 762 763 final List<? extends Node> catches = tryNode.getCatches(); 764 final List<CatchNode> guarded = new ArrayList<>(); 765 CatchNode unguarded = null; 766 if (catches != null) { 767 for (final Node n : catches) { 768 final CatchNode cn = (CatchNode)n; 769 if (cn.getExceptionCondition() != null) { 770 guarded.add(cn); 771 } else { 772 assert unguarded == null: "too many unguarded?"; 773 unguarded = cn; 774 } 775 } 776 } 777 778 array("guardedHandlers", guarded); 779 comma(); 780 781 property("handler"); 782 if (unguarded != null) { 783 unguarded.accept(this); 784 } else { 785 nullValue(); 786 } 787 comma(); 788 789 property("finalizer"); 790 final Node finallyNode = tryNode.getFinallyBody(); 791 if (finallyNode != null) { 792 finallyNode.accept(this); 793 } else { 794 nullValue(); 795 } 796 797 return leave(); 798 } 799 800 @Override enterUnaryNode(final UnaryNode unaryNode)801 public boolean enterUnaryNode(final UnaryNode unaryNode) { 802 enterDefault(unaryNode); 803 804 final TokenType tokenType = unaryNode.tokenType(); 805 if (tokenType == TokenType.NEW) { 806 type("NewExpression"); 807 comma(); 808 809 final CallNode callNode = (CallNode)unaryNode.getExpression(); 810 property("callee"); 811 callNode.getFunction().accept(this); 812 comma(); 813 814 array("arguments", callNode.getArgs()); 815 } else { 816 final String operator; 817 final boolean prefix; 818 switch (tokenType) { 819 case INCPOSTFIX: 820 prefix = false; 821 operator = "++"; 822 break; 823 case DECPOSTFIX: 824 prefix = false; 825 operator = "--"; 826 break; 827 case INCPREFIX: 828 operator = "++"; 829 prefix = true; 830 break; 831 case DECPREFIX: 832 operator = "--"; 833 prefix = true; 834 break; 835 default: 836 prefix = true; 837 operator = tokenType.getName(); 838 break; 839 } 840 841 type(unaryNode.isAssignment()? "UpdateExpression" : "UnaryExpression"); 842 comma(); 843 844 property("operator", operator); 845 comma(); 846 847 property("prefix", prefix); 848 comma(); 849 850 property("argument"); 851 unaryNode.getExpression().accept(this); 852 } 853 854 return leave(); 855 } 856 857 @Override enterVarNode(final VarNode varNode)858 public boolean enterVarNode(final VarNode varNode) { 859 final Node init = varNode.getInit(); 860 if (init instanceof FunctionNode && ((FunctionNode)init).isDeclared()) { 861 // function declaration - don't emit VariableDeclaration instead 862 // just emit FunctionDeclaration using 'init' Node. 863 init.accept(this); 864 return false; 865 } 866 867 enterDefault(varNode); 868 869 type("VariableDeclaration"); 870 comma(); 871 872 arrayStart("declarations"); 873 874 // VariableDeclarator 875 objectStart(); 876 location(varNode.getName()); 877 878 type("VariableDeclarator"); 879 comma(); 880 881 property("id"); 882 varNode.getName().accept(this); 883 comma(); 884 885 property("init"); 886 if (init != null) { 887 init.accept(this); 888 } else { 889 nullValue(); 890 } 891 892 // VariableDeclarator 893 objectEnd(); 894 895 // declarations 896 arrayEnd(); 897 898 return leave(); 899 } 900 901 @Override enterWhileNode(final WhileNode whileNode)902 public boolean enterWhileNode(final WhileNode whileNode) { 903 enterDefault(whileNode); 904 905 type(whileNode.isDoWhile() ? "DoWhileStatement" : "WhileStatement"); 906 comma(); 907 908 if (whileNode.isDoWhile()) { 909 property("body"); 910 whileNode.getBody().accept(this); 911 comma(); 912 913 property("test"); 914 whileNode.getTest().accept(this); 915 } else { 916 property("test"); 917 whileNode.getTest().accept(this); 918 comma(); 919 920 property("body"); 921 whileNode.getBody().accept(this); 922 } 923 924 return leave(); 925 } 926 927 @Override enterWithNode(final WithNode withNode)928 public boolean enterWithNode(final WithNode withNode) { 929 enterDefault(withNode); 930 931 type("WithStatement"); 932 comma(); 933 934 property("object"); 935 withNode.getExpression().accept(this); 936 comma(); 937 938 property("body"); 939 withNode.getBody().accept(this); 940 941 return leave(); 942 } 943 944 // Internals below 945 JSONWriter(final boolean includeLocation)946 private JSONWriter(final boolean includeLocation) { 947 this.buf = new StringBuilder(); 948 this.includeLocation = includeLocation; 949 } 950 951 private final StringBuilder buf; 952 private final boolean includeLocation; 953 getString()954 private String getString() { 955 return buf.toString(); 956 } 957 property(final String key, final String value, final boolean escape)958 private void property(final String key, final String value, final boolean escape) { 959 buf.append('"'); 960 buf.append(key); 961 buf.append("\":"); 962 if (value != null) { 963 if (escape) { 964 buf.append('"'); 965 } 966 buf.append(value); 967 if (escape) { 968 buf.append('"'); 969 } 970 } 971 } 972 property(final String key, final String value)973 private void property(final String key, final String value) { 974 property(key, value, true); 975 } 976 property(final String key, final boolean value)977 private void property(final String key, final boolean value) { 978 property(key, Boolean.toString(value), false); 979 } 980 property(final String key, final int value)981 private void property(final String key, final int value) { 982 property(key, Integer.toString(value), false); 983 } 984 property(final String key)985 private void property(final String key) { 986 property(key, null); 987 } 988 type(final String value)989 private void type(final String value) { 990 property("type", value); 991 } 992 objectStart(final String name)993 private void objectStart(final String name) { 994 buf.append('"'); 995 buf.append(name); 996 buf.append("\":{"); 997 } 998 objectStart()999 private void objectStart() { 1000 buf.append('{'); 1001 } 1002 objectEnd()1003 private void objectEnd() { 1004 buf.append('}'); 1005 } 1006 array(final String name, final List<? extends Node> nodes)1007 private void array(final String name, final List<? extends Node> nodes) { 1008 // The size, idx comparison is just to avoid trailing comma.. 1009 final int size = nodes.size(); 1010 int idx = 0; 1011 arrayStart(name); 1012 for (final Node node : nodes) { 1013 if (node != null) { 1014 node.accept(this); 1015 } else { 1016 nullValue(); 1017 } 1018 if (idx != (size - 1)) { 1019 comma(); 1020 } 1021 idx++; 1022 } 1023 arrayEnd(); 1024 } 1025 arrayStart(final String name)1026 private void arrayStart(final String name) { 1027 buf.append('"'); 1028 buf.append(name); 1029 buf.append('"'); 1030 buf.append(':'); 1031 buf.append('['); 1032 } 1033 arrayEnd()1034 private void arrayEnd() { 1035 buf.append(']'); 1036 } 1037 comma()1038 private void comma() { 1039 buf.append(','); 1040 } 1041 nullValue()1042 private void nullValue() { 1043 buf.append("null"); 1044 } 1045 location(final Node node)1046 private void location(final Node node) { 1047 if (includeLocation) { 1048 objectStart("loc"); 1049 1050 // source name 1051 final Source src = lc.getCurrentFunction().getSource(); 1052 property("source", src.getName()); 1053 comma(); 1054 1055 // start position 1056 objectStart("start"); 1057 final int start = node.getStart(); 1058 property("line", src.getLine(start)); 1059 comma(); 1060 property("column", src.getColumn(start)); 1061 objectEnd(); 1062 comma(); 1063 1064 // end position 1065 objectStart("end"); 1066 final int end = node.getFinish(); 1067 property("line", src.getLine(end)); 1068 comma(); 1069 property("column", src.getColumn(end)); 1070 objectEnd(); 1071 1072 // end 'loc' 1073 objectEnd(); 1074 1075 comma(); 1076 } 1077 } 1078 quote(final String str)1079 private static String quote(final String str) { 1080 return JSONParser.quote(str); 1081 } 1082 } 1083