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