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