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 if (functionNode.isAnonymous()) { 414 nullValue(); 415 } else { 416 functionNode.getIdent().accept(this); 417 } 418 comma(); 419 420 array("params", functionNode.getParameters()); 421 comma(); 422 423 arrayStart("defaults"); 424 arrayEnd(); 425 comma(); 426 427 property("rest"); 428 nullValue(); 429 comma(); 430 431 property("body"); 432 functionNode.getBody().accept(this); 433 comma(); 434 435 property("generator", false); 436 comma(); 437 438 property("expression", false); 439 440 return leave(); 441 } 442 443 private boolean emitProgram(final FunctionNode functionNode) { 444 enterDefault(functionNode); 445 type("Program"); 446 comma(); 447 448 // body consists of nested functions and statements 449 final List<Statement> stats = functionNode.getBody().getStatements(); 450 final int size = stats.size(); 451 int idx = 0; 452 arrayStart("body"); 453 454 for (final Node stat : stats) { 455 stat.accept(this); 456 if (idx != (size - 1)) { 457 comma(); 458 } 459 idx++; 460 } 461 arrayEnd(); 462 463 return leave(); 464 } 465 466 @Override 467 public boolean enterIdentNode(final IdentNode identNode) { 468 enterDefault(identNode); 469 470 final String name = identNode.getName(); 471 if ("this".equals(name)) { 472 type("ThisExpression"); 473 } else { 474 type("Identifier"); 475 comma(); 476 property("name", identNode.getName()); 477 } 478 479 return leave(); 480 } 481 482 @Override 483 public boolean enterIfNode(final IfNode ifNode) { 484 enterDefault(ifNode); 485 486 type("IfStatement"); 487 comma(); 488 489 property("test"); 490 ifNode.getTest().accept(this); 491 comma(); 492 493 property("consequent"); 494 ifNode.getPass().accept(this); 495 final Node elsePart = ifNode.getFail(); 496 comma(); 497 498 property("alternate"); 499 if (elsePart != null) { 500 elsePart.accept(this); 501 } else { 502 nullValue(); 503 } 504 505 return leave(); 506 } 507 508 @Override 509 public boolean enterIndexNode(final IndexNode indexNode) { 510 enterDefault(indexNode); 511 512 type("MemberExpression"); 513 comma(); 514 515 property("object"); 516 indexNode.getBase().accept(this); 517 comma(); 518 519 property("property"); 520 indexNode.getIndex().accept(this); 521 comma(); 522 523 property("computed", true); 524 525 return leave(); 526 } 527 528 @Override 529 public boolean enterLabelNode(final LabelNode labelNode) { 530 enterDefault(labelNode); 531 532 type("LabeledStatement"); 533 comma(); 534 535 property("label"); 536 labelNode.getLabel().accept(this); 537 comma(); 538 539 property("body"); 540 labelNode.getBody().accept(this); 541 542 return leave(); 543 } 544 545 @SuppressWarnings("rawtypes") 546 @Override 547 public boolean enterLiteralNode(final LiteralNode literalNode) { 548 enterDefault(literalNode); 549 550 if (literalNode instanceof LiteralNode.ArrayLiteralNode) { 551 type("ArrayExpression"); 552 comma(); 553 554 final Node[] value = literalNode.getArray(); 555 array("elements", Arrays.asList(value)); 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 (Node n : catches) { 763 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.rhs(); 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.rhs().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) buf.append('"'); 960 buf.append(value); 961 if (escape) buf.append('"'); 962 } 963 } 964 965 private void property(final String key, final String value) { 966 property(key, value, true); 967 } 968 969 private void property(final String key, final boolean value) { 970 property(key, Boolean.toString(value), false); 971 } 972 973 private void property(final String key, final int value) { 974 property(key, Integer.toString(value), false); 975 } 976 977 private void property(final String key) { 978 property(key, null); 979 } 980 981 private void type(final String value) { 982 property("type", value); 983 } 984 985 private void objectStart(final String name) { 986 buf.append('"'); 987 buf.append(name); 988 buf.append("\":{"); 989 } 990 991 private void objectStart() { 992 buf.append('{'); 993 } 994 995 private void objectEnd() { 996 buf.append('}'); 997 } 998 999 private void array(final String name, final List<? extends Node> nodes) { 1000 // The size, idx comparison is just to avoid trailing comma.. 1001 final int size = nodes.size(); 1002 int idx = 0; 1003 arrayStart(name); 1004 for (final Node node : nodes) { 1005 if (node != null) { 1006 node.accept(this); 1007 } else { 1008 nullValue(); 1009 } 1010 if (idx != (size - 1)) { 1011 comma(); 1012 } 1013 idx++; 1014 } 1015 arrayEnd(); 1016 } 1017 1018 private void arrayStart(final String name) { 1019 buf.append('"'); 1020 buf.append(name); 1021 buf.append('"'); 1022 buf.append(':'); 1023 buf.append('['); 1024 } 1025 1026 private void arrayEnd() { 1027 buf.append(']'); 1028 } 1029 1030 private void comma() { 1031 buf.append(','); 1032 } 1033 1034 private void nullValue() { 1035 buf.append("null"); 1036 } 1037 1038 private void location(final Node node) { 1039 if (includeLocation) { 1040 objectStart("loc"); 1041 1042 // source name 1043 final Source src = lc.getCurrentFunction().getSource(); 1044 property("source", src.getName()); 1045 comma(); 1046 1047 // start position 1048 objectStart("start"); 1049 final int start = node.getStart(); 1050 property("line", src.getLine(start)); 1051 comma(); 1052 property("column", src.getColumn(start)); 1053 objectEnd(); 1054 comma(); 1055 1056 // end position 1057 objectStart("end"); 1058 final int end = node.getFinish(); 1059 property("line", src.getLine(end)); 1060 comma(); 1061 property("column", src.getColumn(end)); 1062 objectEnd(); 1063 1064 // end 'loc' 1065 objectEnd(); 1066 1067 comma(); 1068 } 1069 } 1070 1071 private static String quote(final String str) { 1072 return JSONParser.quote(str); 1073 } 1074 }