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