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.EmptyNode; 42 import jdk.nashorn.internal.ir.Expression; 43 import jdk.nashorn.internal.ir.ExpressionStatement; 44 import jdk.nashorn.internal.ir.ForNode; 45 import jdk.nashorn.internal.ir.FunctionNode; 46 import jdk.nashorn.internal.ir.IdentNode; 47 import jdk.nashorn.internal.ir.IfNode; 48 import jdk.nashorn.internal.ir.IndexNode; 49 import jdk.nashorn.internal.ir.JoinPredecessorExpression; 50 import jdk.nashorn.internal.ir.LabelNode; 51 import jdk.nashorn.internal.ir.LexicalContext; 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.NodeVisitor; 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 NodeVisitor<LexicalContext> { 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 */ 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 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 116 protected boolean enterDefault(final Node node) { 117 objectStart(); 118 location(node); 119 120 return true; 121 } 122 123 private boolean leave() { 124 objectEnd(); 125 return false; 126 } 127 128 @Override 129 protected Node leaveDefault(final Node node) { 130 objectEnd(); 131 return null; 132 } 133 134 @Override 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 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 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 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 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 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 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 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 292 public boolean enterEmptyNode(final EmptyNode emptyNode) { 293 enterDefault(emptyNode); 294 295 type("EmptyStatement"); 296 297 return leave(); 298 } 299 300 @Override 301 public boolean enterExpressionStatement(final ExpressionStatement expressionStatement) { 302 // handle debugger statement 303 final Node expression = expressionStatement.getExpression(); 304 if (expression instanceof RuntimeNode) { 305 expression.accept(this); 306 return false; 307 } 308 309 enterDefault(expressionStatement); 310 311 type("ExpressionStatement"); 312 comma(); 313 314 property("expression"); 315 expression.accept(this); 316 317 return leave(); 318 } 319 320 @Override 321 public boolean enterBlockStatement(final BlockStatement blockStatement) { 322 enterDefault(blockStatement); 323 324 type("BlockStatement"); 325 comma(); 326 327 property("block"); 328 blockStatement.getBlock().accept(this); 329 330 return leave(); 331 } 332 333 @Override 334 public boolean enterForNode(final ForNode forNode) { 335 enterDefault(forNode); 336 337 if (forNode.isForIn() || (forNode.isForEach() && forNode.getInit() != null)) { 338 type("ForInStatement"); 339 comma(); 340 341 final Node init = forNode.getInit(); 342 assert init != null; 343 property("left"); 344 init.accept(this); 345 comma(); 346 347 final Node modify = forNode.getModify(); 348 assert modify != null; 349 property("right"); 350 modify.accept(this); 351 comma(); 352 353 property("body"); 354 forNode.getBody().accept(this); 355 comma(); 356 357 property("each", forNode.isForEach()); 358 } else { 359 type("ForStatement"); 360 comma(); 361 362 final Node init = forNode.getInit(); 363 property("init"); 364 if (init != null) { 365 init.accept(this); 366 } else { 367 nullValue(); 368 } 369 comma(); 370 371 final Node test = forNode.getTest(); 372 property("test"); 373 if (test != null) { 374 test.accept(this); 375 } else { 376 nullValue(); 377 } 378 comma(); 379 380 final Node update = forNode.getModify(); 381 property("update"); 382 if (update != null) { 383 update.accept(this); 384 } else { 385 nullValue(); 386 } 387 comma(); 388 389 property("body"); 390 forNode.getBody().accept(this); 391 } 392 393 return leave(); 394 } 395 396 @Override 397 public boolean enterFunctionNode(final FunctionNode functionNode) { 398 final boolean program = functionNode.isProgram(); 399 if (program) { 400 return emitProgram(functionNode); 401 } 402 403 enterDefault(functionNode); 404 final String name; 405 if (functionNode.isDeclared()) { 406 name = "FunctionDeclaration"; 407 } else { 408 name = "FunctionExpression"; 409 } 410 type(name); 411 comma(); 412 413 property("id"); 414 final FunctionNode.Kind kind = functionNode.getKind(); 415 if (functionNode.isAnonymous() || kind == FunctionNode.Kind.GETTER || kind == FunctionNode.Kind.SETTER) { 416 nullValue(); 417 } else { 418 functionNode.getIdent().accept(this); 419 } 420 comma(); 421 422 array("params", functionNode.getParameters()); 423 comma(); 424 425 arrayStart("defaults"); 426 arrayEnd(); 427 comma(); 428 429 property("rest"); 430 nullValue(); 431 comma(); 432 433 property("body"); 434 functionNode.getBody().accept(this); 435 comma(); 436 437 property("generator", false); 438 comma(); 439 440 property("expression", false); 441 442 return leave(); 443 } 444 445 private boolean emitProgram(final FunctionNode functionNode) { 446 enterDefault(functionNode); 447 type("Program"); 448 comma(); 449 450 // body consists of nested functions and statements 451 final List<Statement> stats = functionNode.getBody().getStatements(); 452 final int size = stats.size(); 453 int idx = 0; 454 arrayStart("body"); 455 456 for (final Node stat : stats) { 457 stat.accept(this); 458 if (idx != (size - 1)) { 459 comma(); 460 } 461 idx++; 462 } 463 arrayEnd(); 464 465 return leave(); 466 } 467 468 @Override 469 public boolean enterIdentNode(final IdentNode identNode) { 470 enterDefault(identNode); 471 472 final String name = identNode.getName(); 473 if ("this".equals(name)) { 474 type("ThisExpression"); 475 } else { 476 type("Identifier"); 477 comma(); 478 property("name", identNode.getName()); 479 } 480 481 return leave(); 482 } 483 484 @Override 485 public boolean enterIfNode(final IfNode ifNode) { 486 enterDefault(ifNode); 487 488 type("IfStatement"); 489 comma(); 490 491 property("test"); 492 ifNode.getTest().accept(this); 493 comma(); 494 495 property("consequent"); 496 ifNode.getPass().accept(this); 497 final Node elsePart = ifNode.getFail(); 498 comma(); 499 500 property("alternate"); 501 if (elsePart != null) { 502 elsePart.accept(this); 503 } else { 504 nullValue(); 505 } 506 507 return leave(); 508 } 509 510 @Override 511 public boolean enterIndexNode(final IndexNode indexNode) { 512 enterDefault(indexNode); 513 514 type("MemberExpression"); 515 comma(); 516 517 property("object"); 518 indexNode.getBase().accept(this); 519 comma(); 520 521 property("property"); 522 indexNode.getIndex().accept(this); 523 comma(); 524 525 property("computed", true); 526 527 return leave(); 528 } 529 530 @Override 531 public boolean enterLabelNode(final LabelNode labelNode) { 532 enterDefault(labelNode); 533 534 type("LabeledStatement"); 535 comma(); 536 537 property("label", labelNode.getLabelName()); 538 comma(); 539 540 property("body"); 541 labelNode.getBody().accept(this); 542 543 return leave(); 544 } 545 546 @SuppressWarnings("rawtypes") 547 @Override 548 public boolean enterLiteralNode(final LiteralNode literalNode) { 549 enterDefault(literalNode); 550 551 if (literalNode instanceof LiteralNode.ArrayLiteralNode) { 552 type("ArrayExpression"); 553 comma(); 554 555 array("elements", ((LiteralNode.ArrayLiteralNode)literalNode).getElementExpressions()); 556 } else { 557 type("Literal"); 558 comma(); 559 560 property("value"); 561 final Object value = literalNode.getValue(); 562 if (value instanceof RegexToken) { 563 // encode RegExp literals as Strings of the form /.../<flags> 564 final RegexToken regex = (RegexToken)value; 565 final StringBuilder regexBuf = new StringBuilder(); 566 regexBuf.append('/'); 567 regexBuf.append(regex.getExpression()); 568 regexBuf.append('/'); 569 regexBuf.append(regex.getOptions()); 570 buf.append(quote(regexBuf.toString())); 571 } else { 572 final String str = literalNode.getString(); 573 // encode every String literal with prefix '$' so that script 574 // can differentiate b/w RegExps as Strings and Strings. 575 buf.append(literalNode.isString()? quote("$" + str) : str); 576 } 577 } 578 579 return leave(); 580 } 581 582 @Override 583 public boolean enterObjectNode(final ObjectNode objectNode) { 584 enterDefault(objectNode); 585 586 type("ObjectExpression"); 587 comma(); 588 589 array("properties", objectNode.getElements()); 590 591 return leave(); 592 } 593 594 @Override 595 public boolean enterPropertyNode(final PropertyNode propertyNode) { 596 final Node key = propertyNode.getKey(); 597 598 final Node value = propertyNode.getValue(); 599 if (value != null) { 600 objectStart(); 601 location(propertyNode); 602 603 property("key"); 604 key.accept(this); 605 comma(); 606 607 property("value"); 608 value.accept(this); 609 comma(); 610 611 property("kind", "init"); 612 613 objectEnd(); 614 } else { 615 // getter 616 final Node getter = propertyNode.getGetter(); 617 if (getter != null) { 618 objectStart(); 619 location(propertyNode); 620 621 property("key"); 622 key.accept(this); 623 comma(); 624 625 property("value"); 626 getter.accept(this); 627 comma(); 628 629 property("kind", "get"); 630 631 objectEnd(); 632 } 633 634 // setter 635 final Node setter = propertyNode.getSetter(); 636 if (setter != null) { 637 if (getter != null) { 638 comma(); 639 } 640 objectStart(); 641 location(propertyNode); 642 643 property("key"); 644 key.accept(this); 645 comma(); 646 647 property("value"); 648 setter.accept(this); 649 comma(); 650 651 property("kind", "set"); 652 653 objectEnd(); 654 } 655 } 656 657 return false; 658 } 659 660 @Override 661 public boolean enterReturnNode(final ReturnNode returnNode) { 662 enterDefault(returnNode); 663 664 type("ReturnStatement"); 665 comma(); 666 667 final Node arg = returnNode.getExpression(); 668 property("argument"); 669 if (arg != null) { 670 arg.accept(this); 671 } else { 672 nullValue(); 673 } 674 675 return leave(); 676 } 677 678 @Override 679 public boolean enterRuntimeNode(final RuntimeNode runtimeNode) { 680 final RuntimeNode.Request req = runtimeNode.getRequest(); 681 682 if (req == RuntimeNode.Request.DEBUGGER) { 683 enterDefault(runtimeNode); 684 type("DebuggerStatement"); 685 return leave(); 686 } 687 688 return false; 689 } 690 691 @Override 692 public boolean enterSplitNode(final SplitNode splitNode) { 693 return false; 694 } 695 696 @Override 697 public boolean enterSwitchNode(final SwitchNode switchNode) { 698 enterDefault(switchNode); 699 700 type("SwitchStatement"); 701 comma(); 702 703 property("discriminant"); 704 switchNode.getExpression().accept(this); 705 comma(); 706 707 array("cases", switchNode.getCases()); 708 709 return leave(); 710 } 711 712 @Override 713 public boolean enterTernaryNode(final TernaryNode ternaryNode) { 714 enterDefault(ternaryNode); 715 716 type("ConditionalExpression"); 717 comma(); 718 719 property("test"); 720 ternaryNode.getTest().accept(this); 721 comma(); 722 723 property("consequent"); 724 ternaryNode.getTrueExpression().accept(this); 725 comma(); 726 727 property("alternate"); 728 ternaryNode.getFalseExpression().accept(this); 729 730 return leave(); 731 } 732 733 @Override 734 public boolean enterThrowNode(final ThrowNode throwNode) { 735 enterDefault(throwNode); 736 737 type("ThrowStatement"); 738 comma(); 739 740 property("argument"); 741 throwNode.getExpression().accept(this); 742 743 return leave(); 744 } 745 746 @Override 747 public boolean enterTryNode(final TryNode tryNode) { 748 enterDefault(tryNode); 749 750 type("TryStatement"); 751 comma(); 752 753 property("block"); 754 tryNode.getBody().accept(this); 755 comma(); 756 757 758 final List<? extends Node> catches = tryNode.getCatches(); 759 final List<CatchNode> guarded = new ArrayList<>(); 760 CatchNode unguarded = null; 761 if (catches != null) { 762 for (final Node n : catches) { 763 final CatchNode cn = (CatchNode)n; 764 if (cn.getExceptionCondition() != null) { 765 guarded.add(cn); 766 } else { 767 assert unguarded == null: "too many unguarded?"; 768 unguarded = cn; 769 } 770 } 771 } 772 773 array("guardedHandlers", guarded); 774 comma(); 775 776 property("handler"); 777 if (unguarded != null) { 778 unguarded.accept(this); 779 } else { 780 nullValue(); 781 } 782 comma(); 783 784 property("finalizer"); 785 final Node finallyNode = tryNode.getFinallyBody(); 786 if (finallyNode != null) { 787 finallyNode.accept(this); 788 } else { 789 nullValue(); 790 } 791 792 return leave(); 793 } 794 795 @Override 796 public boolean enterUnaryNode(final UnaryNode unaryNode) { 797 enterDefault(unaryNode); 798 799 final TokenType tokenType = unaryNode.tokenType(); 800 if (tokenType == TokenType.NEW) { 801 type("NewExpression"); 802 comma(); 803 804 final CallNode callNode = (CallNode)unaryNode.getExpression(); 805 property("callee"); 806 callNode.getFunction().accept(this); 807 comma(); 808 809 array("arguments", callNode.getArgs()); 810 } else { 811 final String operator; 812 final boolean prefix; 813 switch (tokenType) { 814 case INCPOSTFIX: 815 prefix = false; 816 operator = "++"; 817 break; 818 case DECPOSTFIX: 819 prefix = false; 820 operator = "--"; 821 break; 822 case INCPREFIX: 823 operator = "++"; 824 prefix = true; 825 break; 826 case DECPREFIX: 827 operator = "--"; 828 prefix = true; 829 break; 830 default: 831 prefix = true; 832 operator = tokenType.getName(); 833 break; 834 } 835 836 type(unaryNode.isAssignment()? "UpdateExpression" : "UnaryExpression"); 837 comma(); 838 839 property("operator", operator); 840 comma(); 841 842 property("prefix", prefix); 843 comma(); 844 845 property("argument"); 846 unaryNode.getExpression().accept(this); 847 } 848 849 return leave(); 850 } 851 852 @Override 853 public boolean enterVarNode(final VarNode varNode) { 854 final Node init = varNode.getInit(); 855 if (init instanceof FunctionNode && ((FunctionNode)init).isDeclared()) { 856 // function declaration - don't emit VariableDeclaration instead 857 // just emit FunctionDeclaration using 'init' Node. 858 init.accept(this); 859 return false; 860 } 861 862 enterDefault(varNode); 863 864 type("VariableDeclaration"); 865 comma(); 866 867 arrayStart("declarations"); 868 869 // VariableDeclarator 870 objectStart(); 871 location(varNode.getName()); 872 873 type("VariableDeclarator"); 874 comma(); 875 876 property("id"); 877 varNode.getName().accept(this); 878 comma(); 879 880 property("init"); 881 if (init != null) { 882 init.accept(this); 883 } else { 884 nullValue(); 885 } 886 887 // VariableDeclarator 888 objectEnd(); 889 890 // declarations 891 arrayEnd(); 892 893 return leave(); 894 } 895 896 @Override 897 public boolean enterWhileNode(final WhileNode whileNode) { 898 enterDefault(whileNode); 899 900 type(whileNode.isDoWhile() ? "DoWhileStatement" : "WhileStatement"); 901 comma(); 902 903 if (whileNode.isDoWhile()) { 904 property("body"); 905 whileNode.getBody().accept(this); 906 comma(); 907 908 property("test"); 909 whileNode.getTest().accept(this); 910 } else { 911 property("test"); 912 whileNode.getTest().accept(this); 913 comma(); 914 915 property("body"); 916 whileNode.getBody().accept(this); 917 } 918 919 return leave(); 920 } 921 922 @Override 923 public boolean enterWithNode(final WithNode withNode) { 924 enterDefault(withNode); 925 926 type("WithStatement"); 927 comma(); 928 929 property("object"); 930 withNode.getExpression().accept(this); 931 comma(); 932 933 property("body"); 934 withNode.getBody().accept(this); 935 936 return leave(); 937 } 938 939 // Internals below 940 941 private JSONWriter(final boolean includeLocation) { 942 super(new LexicalContext()); 943 this.buf = new StringBuilder(); 944 this.includeLocation = includeLocation; 945 } 946 947 private final StringBuilder buf; 948 private final boolean includeLocation; 949 950 private String getString() { 951 return buf.toString(); 952 } 953 954 private void property(final String key, final String value, final boolean escape) { 955 buf.append('"'); 956 buf.append(key); 957 buf.append("\":"); 958 if (value != null) { 959 if (escape) { 960 buf.append('"'); 961 } 962 buf.append(value); 963 if (escape) { 964 buf.append('"'); 965 } 966 } 967 } 968 969 private void property(final String key, final String value) { 970 property(key, value, true); 971 } 972 973 private void property(final String key, final boolean value) { 974 property(key, Boolean.toString(value), false); 975 } 976 977 private void property(final String key, final int value) { 978 property(key, Integer.toString(value), false); 979 } 980 981 private void property(final String key) { 982 property(key, null); 983 } 984 985 private void type(final String value) { 986 property("type", value); 987 } 988 989 private void objectStart(final String name) { 990 buf.append('"'); 991 buf.append(name); 992 buf.append("\":{"); 993 } 994 995 private void objectStart() { 996 buf.append('{'); 997 } 998 999 private void objectEnd() { 1000 buf.append('}'); 1001 } 1002 1003 private void array(final String name, final List<? extends Node> nodes) { 1004 // The size, idx comparison is just to avoid trailing comma.. 1005 final int size = nodes.size(); 1006 int idx = 0; 1007 arrayStart(name); 1008 for (final Node node : nodes) { 1009 if (node != null) { 1010 node.accept(this); 1011 } else { 1012 nullValue(); 1013 } 1014 if (idx != (size - 1)) { 1015 comma(); 1016 } 1017 idx++; 1018 } 1019 arrayEnd(); 1020 } 1021 1022 private void arrayStart(final String name) { 1023 buf.append('"'); 1024 buf.append(name); 1025 buf.append('"'); 1026 buf.append(':'); 1027 buf.append('['); 1028 } 1029 1030 private void arrayEnd() { 1031 buf.append(']'); 1032 } 1033 1034 private void comma() { 1035 buf.append(','); 1036 } 1037 1038 private void nullValue() { 1039 buf.append("null"); 1040 } 1041 1042 private void location(final Node node) { 1043 if (includeLocation) { 1044 objectStart("loc"); 1045 1046 // source name 1047 final Source src = lc.getCurrentFunction().getSource(); 1048 property("source", src.getName()); 1049 comma(); 1050 1051 // start position 1052 objectStart("start"); 1053 final int start = node.getStart(); 1054 property("line", src.getLine(start)); 1055 comma(); 1056 property("column", src.getColumn(start)); 1057 objectEnd(); 1058 comma(); 1059 1060 // end position 1061 objectStart("end"); 1062 final int end = node.getFinish(); 1063 property("line", src.getLine(end)); 1064 comma(); 1065 property("column", src.getColumn(end)); 1066 objectEnd(); 1067 1068 // end 'loc' 1069 objectEnd(); 1070 1071 comma(); 1072 } 1073 } 1074 1075 private static String quote(final String str) { 1076 return JSONParser.quote(str); 1077 } 1078 }