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