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.EmptyNode;
  42 import jdk.nashorn.internal.ir.Expression;
  43 import jdk.nashorn.internal.ir.ExpressionStatement;
  44 import jdk.nashorn.internal.ir.ForNode;
  45 import jdk.nashorn.internal.ir.FunctionNode;
  46 import jdk.nashorn.internal.ir.IdentNode;
  47 import jdk.nashorn.internal.ir.IfNode;
  48 import jdk.nashorn.internal.ir.IndexNode;
  49 import jdk.nashorn.internal.ir.JoinPredecessorExpression;
  50 import jdk.nashorn.internal.ir.LabelNode;
  51 import jdk.nashorn.internal.ir.LexicalContext;
  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.NodeVisitor;
  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 NodeVisitor<LexicalContext> {
  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 enterEmptyNode(final EmptyNode emptyNode) {
 293         enterDefault(emptyNode);
 294 
 295         type("EmptyStatement");
 296 
 297         return leave();
 298     }
 299 
 300     @Override
 301     public boolean enterExpressionStatement(final ExpressionStatement expressionStatement) {
 302         // handle debugger statement
 303         final Node expression = expressionStatement.getExpression();
 304         if (expression instanceof RuntimeNode) {
 305             expression.accept(this);
 306             return false;
 307         }
 308 
 309         enterDefault(expressionStatement);
 310 
 311         type("ExpressionStatement");
 312         comma();
 313 
 314         property("expression");
 315         expression.accept(this);
 316 
 317         return leave();
 318     }
 319 
 320     @Override
 321     public boolean enterBlockStatement(final BlockStatement blockStatement) {
 322         enterDefault(blockStatement);
 323 
 324         type("BlockStatement");
 325         comma();
 326 
 327         property("block");
 328         blockStatement.getBlock().accept(this);
 329 
 330         return leave();
 331     }
 332 
 333     @Override
 334     public boolean enterForNode(final ForNode forNode) {
 335         enterDefault(forNode);
 336 
 337         if (forNode.isForIn() || (forNode.isForEach() && forNode.getInit() != null)) {
 338             type("ForInStatement");
 339             comma();
 340 
 341             final Node init = forNode.getInit();
 342             assert init != null;
 343             property("left");
 344             init.accept(this);
 345             comma();
 346 
 347             final Node modify = forNode.getModify();
 348             assert modify != null;
 349             property("right");
 350             modify.accept(this);
 351             comma();
 352 
 353             property("body");
 354             forNode.getBody().accept(this);
 355             comma();
 356 
 357             property("each", forNode.isForEach());
 358         } else {
 359             type("ForStatement");
 360             comma();
 361 
 362             final Node init = forNode.getInit();
 363             property("init");
 364             if (init != null) {
 365                 init.accept(this);
 366             } else {
 367                 nullValue();
 368             }
 369             comma();
 370 
 371             final Node test = forNode.getTest();
 372             property("test");
 373             if (test != null) {
 374                 test.accept(this);
 375             } else {
 376                 nullValue();
 377             }
 378             comma();
 379 
 380             final Node update = forNode.getModify();
 381             property("update");
 382             if (update != null) {
 383                 update.accept(this);
 384             } else {
 385                 nullValue();
 386             }
 387             comma();
 388 
 389             property("body");
 390             forNode.getBody().accept(this);
 391         }
 392 
 393         return leave();
 394     }
 395 
 396     @Override
 397     public boolean enterFunctionNode(final FunctionNode functionNode) {
 398         final boolean program = functionNode.isProgram();
 399         if (program) {
 400             return emitProgram(functionNode);
 401         }
 402 
 403         enterDefault(functionNode);
 404         final String name;
 405         if (functionNode.isDeclared()) {
 406             name = "FunctionDeclaration";
 407         } else {
 408             name = "FunctionExpression";
 409         }
 410         type(name);
 411         comma();
 412 
 413         property("id");
 414         final FunctionNode.Kind kind = functionNode.getKind();
 415         if (functionNode.isAnonymous() || kind == FunctionNode.Kind.GETTER || kind == FunctionNode.Kind.SETTER) {
 416             nullValue();
 417         } else {
 418             functionNode.getIdent().accept(this);
 419         }
 420         comma();
 421 
 422         array("params", functionNode.getParameters());
 423         comma();
 424 
 425         arrayStart("defaults");
 426         arrayEnd();
 427         comma();
 428 
 429         property("rest");
 430         nullValue();
 431         comma();
 432 
 433         property("body");
 434         functionNode.getBody().accept(this);
 435         comma();
 436 
 437         property("generator", false);
 438         comma();
 439 
 440         property("expression", false);
 441 
 442         return leave();
 443     }
 444 
 445     private boolean emitProgram(final FunctionNode functionNode) {
 446         enterDefault(functionNode);
 447         type("Program");
 448         comma();
 449 
 450         // body consists of nested functions and statements
 451         final List<Statement> stats = functionNode.getBody().getStatements();
 452         final int size = stats.size();
 453         int idx = 0;
 454         arrayStart("body");
 455 
 456         for (final Node stat : stats) {
 457             stat.accept(this);
 458             if (idx != (size - 1)) {
 459                 comma();
 460             }
 461             idx++;
 462         }
 463         arrayEnd();
 464 
 465         return leave();
 466     }
 467 
 468     @Override
 469     public boolean enterIdentNode(final IdentNode identNode) {
 470         enterDefault(identNode);
 471 
 472         final String name = identNode.getName();
 473         if ("this".equals(name)) {
 474             type("ThisExpression");
 475         } else {
 476             type("Identifier");
 477             comma();
 478             property("name", identNode.getName());
 479         }
 480 
 481         return leave();
 482     }
 483 
 484     @Override
 485     public boolean enterIfNode(final IfNode ifNode) {
 486         enterDefault(ifNode);
 487 
 488         type("IfStatement");
 489         comma();
 490 
 491         property("test");
 492         ifNode.getTest().accept(this);
 493         comma();
 494 
 495         property("consequent");
 496         ifNode.getPass().accept(this);
 497         final Node elsePart = ifNode.getFail();
 498         comma();
 499 
 500         property("alternate");
 501         if (elsePart != null) {
 502             elsePart.accept(this);
 503         } else {
 504             nullValue();
 505         }
 506 
 507         return leave();
 508     }
 509 
 510     @Override
 511     public boolean enterIndexNode(final IndexNode indexNode) {
 512         enterDefault(indexNode);
 513 
 514         type("MemberExpression");
 515         comma();
 516 
 517         property("object");
 518         indexNode.getBase().accept(this);
 519         comma();
 520 
 521         property("property");
 522         indexNode.getIndex().accept(this);
 523         comma();
 524 
 525         property("computed", true);
 526 
 527         return leave();
 528     }
 529 
 530     @Override
 531     public boolean enterLabelNode(final LabelNode labelNode) {
 532         enterDefault(labelNode);
 533 
 534         type("LabeledStatement");
 535         comma();
 536 
 537         property("label", labelNode.getLabelName());
 538         comma();
 539 
 540         property("body");
 541         labelNode.getBody().accept(this);
 542 
 543         return leave();
 544     }
 545 
 546     @SuppressWarnings("rawtypes")
 547     @Override
 548     public boolean enterLiteralNode(final LiteralNode literalNode) {
 549         enterDefault(literalNode);
 550 
 551         if (literalNode instanceof LiteralNode.ArrayLiteralNode) {
 552             type("ArrayExpression");
 553             comma();
 554 
 555             array("elements", ((LiteralNode.ArrayLiteralNode)literalNode).getElementExpressions());
 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 (final Node n : catches) {
 763                 final 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.getExpression();
 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.getExpression().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) {
 960                 buf.append('"');
 961             }
 962             buf.append(value);
 963             if (escape) {
 964                 buf.append('"');
 965             }
 966         }
 967     }
 968 
 969     private void property(final String key, final String value) {
 970         property(key, value, true);
 971     }
 972 
 973     private void property(final String key, final boolean value) {
 974         property(key, Boolean.toString(value), false);
 975     }
 976 
 977     private void property(final String key, final int value) {
 978         property(key, Integer.toString(value), false);
 979     }
 980 
 981     private void property(final String key) {
 982         property(key, null);
 983     }
 984 
 985     private void type(final String value) {
 986         property("type", value);
 987     }
 988 
 989     private void objectStart(final String name) {
 990         buf.append('"');
 991         buf.append(name);
 992         buf.append("\":{");
 993     }
 994 
 995     private void objectStart() {
 996         buf.append('{');
 997     }
 998 
 999     private void objectEnd() {
1000         buf.append('}');
1001     }
1002 
1003     private void array(final String name, final List<? extends Node> nodes) {
1004         // The size, idx comparison is just to avoid trailing comma..
1005         final int size = nodes.size();
1006         int idx = 0;
1007         arrayStart(name);
1008         for (final Node node : nodes) {
1009             if (node != null) {
1010                 node.accept(this);
1011             } else {
1012                 nullValue();
1013             }
1014             if (idx != (size - 1)) {
1015                 comma();
1016             }
1017             idx++;
1018         }
1019         arrayEnd();
1020     }
1021 
1022     private void arrayStart(final String name) {
1023         buf.append('"');
1024         buf.append(name);
1025         buf.append('"');
1026         buf.append(':');
1027         buf.append('[');
1028     }
1029 
1030     private void arrayEnd() {
1031         buf.append(']');
1032     }
1033 
1034     private void comma() {
1035         buf.append(',');
1036     }
1037 
1038     private void nullValue() {
1039         buf.append("null");
1040     }
1041 
1042     private void location(final Node node) {
1043         if (includeLocation) {
1044             objectStart("loc");
1045 
1046             // source name
1047             final Source src = lc.getCurrentFunction().getSource();
1048             property("source", src.getName());
1049             comma();
1050 
1051             // start position
1052             objectStart("start");
1053             final int start = node.getStart();
1054             property("line", src.getLine(start));
1055             comma();
1056             property("column", src.getColumn(start));
1057             objectEnd();
1058             comma();
1059 
1060             // end position
1061             objectStart("end");
1062             final int end = node.getFinish();
1063             property("line", src.getLine(end));
1064             comma();
1065             property("column", src.getColumn(end));
1066             objectEnd();
1067 
1068             // end 'loc'
1069             objectEnd();
1070 
1071             comma();
1072         }
1073     }
1074 
1075     private static String quote(final String str) {
1076         return JSONParser.quote(str);
1077     }
1078 }