1 /*
   2  * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.nashorn.internal.ir.debug;
  27 
  28 import java.util.Arrays;
  29 import java.util.List;
  30 import java.util.ArrayList;
  31 import jdk.nashorn.internal.codegen.CompilerConstants;
  32 import jdk.nashorn.internal.ir.AccessNode;
  33 import jdk.nashorn.internal.ir.BinaryNode;
  34 import jdk.nashorn.internal.ir.Block;
  35 import jdk.nashorn.internal.ir.BlockStatement;
  36 import jdk.nashorn.internal.ir.BreakNode;
  37 import jdk.nashorn.internal.ir.CallNode;
  38 import jdk.nashorn.internal.ir.CaseNode;
  39 import jdk.nashorn.internal.ir.CatchNode;
  40 import jdk.nashorn.internal.ir.ContinueNode;
  41 import jdk.nashorn.internal.ir.EmptyNode;
  42 import jdk.nashorn.internal.ir.ExpressionStatement;
  43 import jdk.nashorn.internal.ir.ForNode;
  44 import jdk.nashorn.internal.ir.FunctionNode;
  45 import jdk.nashorn.internal.ir.IdentNode;
  46 import jdk.nashorn.internal.ir.IfNode;
  47 import jdk.nashorn.internal.ir.IndexNode;
  48 import jdk.nashorn.internal.ir.LabelNode;
  49 import jdk.nashorn.internal.ir.LexicalContext;
  50 import jdk.nashorn.internal.ir.LiteralNode;
  51 import jdk.nashorn.internal.ir.Node;
  52 import jdk.nashorn.internal.ir.ObjectNode;
  53 import jdk.nashorn.internal.ir.PropertyNode;
  54 import jdk.nashorn.internal.ir.ReturnNode;
  55 import jdk.nashorn.internal.ir.RuntimeNode;
  56 import jdk.nashorn.internal.ir.SplitNode;
  57 import jdk.nashorn.internal.ir.Statement;
  58 import jdk.nashorn.internal.ir.SwitchNode;
  59 import jdk.nashorn.internal.ir.TernaryNode;
  60 import jdk.nashorn.internal.ir.ThrowNode;
  61 import jdk.nashorn.internal.ir.TryNode;
  62 import jdk.nashorn.internal.ir.UnaryNode;
  63 import jdk.nashorn.internal.ir.VarNode;
  64 import jdk.nashorn.internal.ir.WhileNode;
  65 import jdk.nashorn.internal.ir.WithNode;
  66 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
  67 import jdk.nashorn.internal.parser.JSONParser;
  68 import jdk.nashorn.internal.parser.Lexer.RegexToken;
  69 import jdk.nashorn.internal.parser.Parser;
  70 import jdk.nashorn.internal.parser.TokenType;
  71 import jdk.nashorn.internal.runtime.Context;
  72 import jdk.nashorn.internal.runtime.ParserException;
  73 import jdk.nashorn.internal.runtime.ScriptEnvironment;
  74 import jdk.nashorn.internal.runtime.Source;
  75 
  76 /**
  77  * This IR writer produces a JSON string that represents AST as a JSON string.
  78  */
  79 public final class JSONWriter extends NodeVisitor<LexicalContext> {
  80 
  81     /**
  82      * Returns AST as JSON compatible string.
  83      *
  84      * @param env  script environment to use
  85      * @param code code to be parsed
  86      * @param name name of the code source (used for location)
  87      * @param includeLoc tells whether to include location information for nodes or not
  88      * @return JSON string representation of AST of the supplied code
  89      */
  90     public static String parse(final ScriptEnvironment env, final String code, final String name, final boolean includeLoc) {
  91         final Parser       parser     = new Parser(env, new Source(name, code), new Context.ThrowErrorManager(), env._strict);
  92         final JSONWriter   jsonWriter = new JSONWriter(includeLoc);
  93         try {
  94             final FunctionNode functionNode = parser.parse(CompilerConstants.RUN_SCRIPT.symbolName());
  95             functionNode.accept(jsonWriter);
  96             return jsonWriter.getString();
  97         } catch (final ParserException e) {
  98             e.throwAsEcmaException();
  99             return null;
 100         }
 101     }
 102 
 103     @Override
 104     protected boolean enterDefault(final Node node) {
 105         objectStart();
 106         location(node);
 107 
 108         return true;
 109     }
 110 
 111     private boolean leave() {
 112         objectEnd();
 113         return false;
 114     }
 115 
 116     @Override
 117     protected Node leaveDefault(final Node node) {
 118         objectEnd();
 119         return null;
 120     }
 121 
 122     @Override
 123     public boolean enterAccessNode(final AccessNode accessNode) {
 124         enterDefault(accessNode);
 125 
 126         type("MemberExpression");
 127         comma();
 128 
 129         property("object");
 130         accessNode.getBase().accept(this);
 131         comma();
 132 
 133         property("property");
 134         accessNode.getProperty().accept(this);
 135         comma();
 136 
 137         property("computed", false);
 138 
 139         return leave();
 140     }
 141 
 142     @Override
 143     public boolean enterBlock(final Block block) {
 144         enterDefault(block);
 145 
 146         type("BlockStatement");
 147         comma();
 148 
 149         array("body", block.getStatements());
 150 
 151         return leave();
 152     }
 153 
 154     private static boolean isLogical(final TokenType tt) {
 155         switch (tt) {
 156         case AND:
 157         case OR:
 158             return true;
 159         default:
 160             return false;
 161         }
 162     }
 163 
 164     @Override
 165     public boolean enterBinaryNode(final BinaryNode binaryNode) {
 166         enterDefault(binaryNode);
 167 
 168         final String name;
 169         if (binaryNode.isAssignment()) {
 170             name = "AssignmentExpression";
 171         } else if (isLogical(binaryNode.tokenType())) {
 172             name = "LogicalExpression";
 173         } else {
 174             name = "BinaryExpression";
 175         }
 176 
 177         type(name);
 178         comma();
 179 
 180         property("operator", binaryNode.tokenType().getName());
 181         comma();
 182 
 183         property("left");
 184         binaryNode.lhs().accept(this);
 185         comma();
 186 
 187         property("right");
 188         binaryNode.rhs().accept(this);
 189 
 190         return leave();
 191     }
 192 
 193     @Override
 194     public boolean enterBreakNode(final BreakNode breakNode) {
 195         enterDefault(breakNode);
 196 
 197         type("BreakStatement");
 198         comma();
 199 
 200         final IdentNode label = breakNode.getLabel();
 201         property("label");
 202         if (label != null) {
 203             label.accept(this);
 204         } else {
 205             nullValue();
 206         }
 207 
 208         return leave();
 209     }
 210 
 211     @Override
 212     public boolean enterCallNode(final CallNode callNode) {
 213         enterDefault(callNode);
 214 
 215         type("CallExpression");
 216         comma();
 217 
 218         property("callee");
 219         callNode.getFunction().accept(this);
 220         comma();
 221 
 222         array("arguments", callNode.getArgs());
 223 
 224         return leave();
 225     }
 226 
 227     @Override
 228     public boolean enterCaseNode(final CaseNode caseNode) {
 229         enterDefault(caseNode);
 230 
 231         type("SwitchCase");
 232         comma();
 233 
 234         final Node test = caseNode.getTest();
 235         property("test");
 236         if (test != null) {
 237             test.accept(this);
 238         } else {
 239             nullValue();
 240         }
 241         comma();
 242 
 243         array("consequent", caseNode.getBody().getStatements());
 244 
 245         return leave();
 246     }
 247 
 248     @Override
 249     public boolean enterCatchNode(final CatchNode catchNode) {
 250         enterDefault(catchNode);
 251 
 252         type("CatchClause");
 253         comma();
 254 
 255         property("param");
 256         catchNode.getException().accept(this);
 257         comma();
 258 
 259         final Node guard = catchNode.getExceptionCondition();
 260         if (guard != null) {
 261             property("guard");
 262             guard.accept(this);
 263             comma();
 264         }
 265 
 266         property("body");
 267         catchNode.getBody().accept(this);
 268 
 269         return leave();
 270     }
 271 
 272     @Override
 273     public boolean enterContinueNode(final ContinueNode continueNode) {
 274         enterDefault(continueNode);
 275 
 276         type("ContinueStatement");
 277         comma();
 278 
 279         final IdentNode label = continueNode.getLabel();
 280         property("label");
 281         if (label != null) {
 282             label.accept(this);
 283         } else {
 284             nullValue();
 285         }
 286 
 287         return leave();
 288     }
 289 
 290     @Override
 291     public boolean enterEmptyNode(final EmptyNode emptyNode) {
 292         enterDefault(emptyNode);
 293 
 294         type("EmptyStatement");
 295 
 296         return leave();
 297     }
 298 
 299     @Override
 300     public boolean enterExpressionStatement(final ExpressionStatement expressionStatement) {
 301         // handle debugger statement
 302         final Node expression = expressionStatement.getExpression();
 303         if (expression instanceof RuntimeNode) {
 304             expression.accept(this);
 305             return false;
 306         }
 307 
 308         enterDefault(expressionStatement);
 309 
 310         type("ExpressionStatement");
 311         comma();
 312 
 313         property("expression");
 314         expression.accept(this);
 315 
 316         return leave();
 317     }
 318 
 319     @Override
 320     public boolean enterBlockStatement(BlockStatement blockStatement) {
 321         enterDefault(blockStatement);
 322 
 323         type("BlockStatement");
 324         comma();
 325 
 326         property("block");
 327         blockStatement.getBlock().accept(this);
 328 
 329         return leave();
 330     }
 331 
 332     @Override
 333     public boolean enterForNode(final ForNode forNode) {
 334         enterDefault(forNode);
 335 
 336         if (forNode.isForIn() || (forNode.isForEach() && forNode.getInit() != null)) {
 337             type("ForInStatement");
 338             comma();
 339 
 340             Node init = forNode.getInit();
 341             assert init != null;
 342             property("left");
 343             init.accept(this);
 344             comma();
 345 
 346             Node modify = forNode.getModify();
 347             assert modify != null;
 348             property("right");
 349             modify.accept(this);
 350             comma();
 351 
 352             property("body");
 353             forNode.getBody().accept(this);
 354             comma();
 355 
 356             property("each", forNode.isForEach());
 357         } else {
 358             type("ForStatement");
 359             comma();
 360 
 361             final Node init = forNode.getInit();
 362             property("init");
 363             if (init != null) {
 364                 init.accept(this);
 365             } else {
 366                 nullValue();
 367             }
 368             comma();
 369 
 370             final Node test = forNode.getTest();
 371             property("test");
 372             if (test != null) {
 373                 test.accept(this);
 374             } else {
 375                 nullValue();
 376             }
 377             comma();
 378 
 379             final Node update = forNode.getModify();
 380             property("update");
 381             if (update != null) {
 382                 update.accept(this);
 383             } else {
 384                 nullValue();
 385             }
 386             comma();
 387 
 388             property("body");
 389             forNode.getBody().accept(this);
 390         }
 391 
 392         return leave();
 393     }
 394 
 395     @Override
 396     public boolean enterFunctionNode(final FunctionNode functionNode) {
 397         final boolean program = functionNode.isProgram();
 398         if (program) {
 399             return emitProgram(functionNode);
 400         }
 401 
 402         enterDefault(functionNode);
 403         final String name;
 404         if (functionNode.isDeclared()) {
 405             name = "FunctionDeclaration";
 406         } else {
 407             name = "FunctionExpression";
 408         }
 409         type(name);
 410         comma();
 411 
 412         property("id");
 413         if (functionNode.isAnonymous()) {

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