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