JSONWriter.java revision 1444:f436923c391f
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
26package jdk.nashorn.internal.ir.debug;
27
28import static jdk.nashorn.internal.runtime.Source.sourceFor;
29
30import java.util.ArrayList;
31import java.util.List;
32import jdk.nashorn.internal.ir.AccessNode;
33import jdk.nashorn.internal.ir.BinaryNode;
34import jdk.nashorn.internal.ir.Block;
35import jdk.nashorn.internal.ir.BlockStatement;
36import jdk.nashorn.internal.ir.BreakNode;
37import jdk.nashorn.internal.ir.CallNode;
38import jdk.nashorn.internal.ir.CaseNode;
39import jdk.nashorn.internal.ir.CatchNode;
40import jdk.nashorn.internal.ir.ContinueNode;
41import jdk.nashorn.internal.ir.DebuggerNode;
42import jdk.nashorn.internal.ir.EmptyNode;
43import jdk.nashorn.internal.ir.Expression;
44import jdk.nashorn.internal.ir.ExpressionStatement;
45import jdk.nashorn.internal.ir.ForNode;
46import jdk.nashorn.internal.ir.FunctionNode;
47import jdk.nashorn.internal.ir.IdentNode;
48import jdk.nashorn.internal.ir.IfNode;
49import jdk.nashorn.internal.ir.IndexNode;
50import jdk.nashorn.internal.ir.JoinPredecessorExpression;
51import jdk.nashorn.internal.ir.LabelNode;
52import jdk.nashorn.internal.ir.LiteralNode;
53import jdk.nashorn.internal.ir.Node;
54import jdk.nashorn.internal.ir.ObjectNode;
55import jdk.nashorn.internal.ir.PropertyNode;
56import jdk.nashorn.internal.ir.ReturnNode;
57import jdk.nashorn.internal.ir.RuntimeNode;
58import jdk.nashorn.internal.ir.SplitNode;
59import jdk.nashorn.internal.ir.Statement;
60import jdk.nashorn.internal.ir.SwitchNode;
61import jdk.nashorn.internal.ir.TernaryNode;
62import jdk.nashorn.internal.ir.ThrowNode;
63import jdk.nashorn.internal.ir.TryNode;
64import jdk.nashorn.internal.ir.UnaryNode;
65import jdk.nashorn.internal.ir.VarNode;
66import jdk.nashorn.internal.ir.WhileNode;
67import jdk.nashorn.internal.ir.WithNode;
68import jdk.nashorn.internal.ir.visitor.SimpleNodeVisitor;
69import jdk.nashorn.internal.parser.JSONParser;
70import jdk.nashorn.internal.parser.Lexer.RegexToken;
71import jdk.nashorn.internal.parser.Parser;
72import jdk.nashorn.internal.parser.TokenType;
73import jdk.nashorn.internal.runtime.Context;
74import jdk.nashorn.internal.runtime.ParserException;
75import jdk.nashorn.internal.runtime.Source;
76
77/**
78 * This IR writer produces a JSON string that represents AST as a JSON string.
79 */
80public final class JSONWriter extends SimpleNodeVisitor {
81
82    /**
83     * Returns AST as JSON compatible string.
84     *
85     * @param context context
86     * @param code code to be parsed
87     * @param name name of the code source (used for location)
88     * @param includeLoc tells whether to include location information for nodes or not
89     * @return JSON string representation of AST of the supplied code
90     */
91    public static String parse(final Context context, final String code, final String name, final boolean includeLoc) {
92        final Parser       parser     = new Parser(context.getEnv(), sourceFor(name, code), new Context.ThrowErrorManager(), context.getEnv()._strict, context.getLogger(Parser.class));
93        final JSONWriter   jsonWriter = new JSONWriter(includeLoc);
94        try {
95            final FunctionNode functionNode = parser.parse(); //symbol name is ":program", default
96            functionNode.accept(jsonWriter);
97            return jsonWriter.getString();
98        } catch (final ParserException e) {
99            e.throwAsEcmaException();
100            return null;
101        }
102    }
103
104    @Override
105    public boolean enterJoinPredecessorExpression(final JoinPredecessorExpression joinPredecessorExpression) {
106        final Expression expr = joinPredecessorExpression.getExpression();
107        if(expr != null) {
108            expr.accept(this);
109        } else {
110            nullValue();
111        }
112        return false;
113    }
114
115    @Override
116    protected boolean enterDefault(final Node node) {
117        objectStart();
118        location(node);
119
120        return true;
121    }
122
123    private boolean leave() {
124        objectEnd();
125        return false;
126    }
127
128    @Override
129    protected Node leaveDefault(final Node node) {
130        objectEnd();
131        return null;
132    }
133
134    @Override
135    public boolean enterAccessNode(final AccessNode accessNode) {
136        enterDefault(accessNode);
137
138        type("MemberExpression");
139        comma();
140
141        property("object");
142        accessNode.getBase().accept(this);
143        comma();
144
145        property("property", accessNode.getProperty());
146        comma();
147
148        property("computed", false);
149
150        return leave();
151    }
152
153    @Override
154    public boolean enterBlock(final Block block) {
155        enterDefault(block);
156
157        type("BlockStatement");
158        comma();
159
160        array("body", block.getStatements());
161
162        return leave();
163    }
164
165    @Override
166    public boolean enterBinaryNode(final BinaryNode binaryNode) {
167        enterDefault(binaryNode);
168
169        final String name;
170        if (binaryNode.isAssignment()) {
171            name = "AssignmentExpression";
172        } else if (binaryNode.isLogical()) {
173            name = "LogicalExpression";
174        } else {
175            name = "BinaryExpression";
176        }
177
178        type(name);
179        comma();
180
181        property("operator", binaryNode.tokenType().getName());
182        comma();
183
184        property("left");
185        binaryNode.lhs().accept(this);
186        comma();
187
188        property("right");
189        binaryNode.rhs().accept(this);
190
191        return leave();
192    }
193
194    @Override
195    public boolean enterBreakNode(final BreakNode breakNode) {
196        enterDefault(breakNode);
197
198        type("BreakStatement");
199        comma();
200
201        final String label = breakNode.getLabelName();
202        if(label != null) {
203            property("label", label);
204        } else {
205            property("label");
206            nullValue();
207        }
208
209        return leave();
210    }
211
212    @Override
213    public boolean enterCallNode(final CallNode callNode) {
214        enterDefault(callNode);
215
216        type("CallExpression");
217        comma();
218
219        property("callee");
220        callNode.getFunction().accept(this);
221        comma();
222
223        array("arguments", callNode.getArgs());
224
225        return leave();
226    }
227
228    @Override
229    public boolean enterCaseNode(final CaseNode caseNode) {
230        enterDefault(caseNode);
231
232        type("SwitchCase");
233        comma();
234
235        final Node test = caseNode.getTest();
236        property("test");
237        if (test != null) {
238            test.accept(this);
239        } else {
240            nullValue();
241        }
242        comma();
243
244        array("consequent", caseNode.getBody().getStatements());
245
246        return leave();
247    }
248
249    @Override
250    public boolean enterCatchNode(final CatchNode catchNode) {
251        enterDefault(catchNode);
252
253        type("CatchClause");
254        comma();
255
256        property("param");
257        catchNode.getException().accept(this);
258        comma();
259
260        final Node guard = catchNode.getExceptionCondition();
261        if (guard != null) {
262            property("guard");
263            guard.accept(this);
264            comma();
265        }
266
267        property("body");
268        catchNode.getBody().accept(this);
269
270        return leave();
271    }
272
273    @Override
274    public boolean enterContinueNode(final ContinueNode continueNode) {
275        enterDefault(continueNode);
276
277        type("ContinueStatement");
278        comma();
279
280        final String label = continueNode.getLabelName();
281        if(label != null) {
282            property("label", label);
283        } else {
284            property("label");
285            nullValue();
286        }
287
288        return leave();
289    }
290
291    @Override
292    public boolean enterDebuggerNode(final DebuggerNode debuggerNode) {
293        enterDefault(debuggerNode);
294        type("DebuggerStatement");
295        return leave();
296    }
297
298    @Override
299    public boolean enterEmptyNode(final EmptyNode emptyNode) {
300        enterDefault(emptyNode);
301
302        type("EmptyStatement");
303
304        return leave();
305    }
306
307    @Override
308    public boolean enterExpressionStatement(final ExpressionStatement expressionStatement) {
309        // handle debugger statement
310        final Node expression = expressionStatement.getExpression();
311        if (expression instanceof RuntimeNode) {
312            assert false : "should not reach here: RuntimeNode";
313            return false;
314        }
315
316        enterDefault(expressionStatement);
317
318        type("ExpressionStatement");
319        comma();
320
321        property("expression");
322        expression.accept(this);
323
324        return leave();
325    }
326
327    @Override
328    public boolean enterBlockStatement(final BlockStatement blockStatement) {
329        if (blockStatement.isSynthetic()) {
330            final Block blk = blockStatement.getBlock();
331            blk.getStatements().get(0).accept(this);
332            return false;
333        }
334
335        enterDefault(blockStatement);
336
337        type("BlockStatement");
338        comma();
339
340        array("body", blockStatement.getBlock().getStatements());
341        return leave();
342    }
343
344    @Override
345    public boolean enterForNode(final ForNode forNode) {
346        enterDefault(forNode);
347
348        if (forNode.isForIn() || (forNode.isForEach() && forNode.getInit() != null)) {
349            type("ForInStatement");
350            comma();
351
352            final Node init = forNode.getInit();
353            assert init != null;
354            property("left");
355            init.accept(this);
356            comma();
357
358            final Node modify = forNode.getModify();
359            assert modify != null;
360            property("right");
361            modify.accept(this);
362            comma();
363
364            property("body");
365            forNode.getBody().accept(this);
366            comma();
367
368            property("each", forNode.isForEach());
369        } else {
370            type("ForStatement");
371            comma();
372
373            final Node init = forNode.getInit();
374            property("init");
375            if (init != null) {
376                init.accept(this);
377            } else {
378                nullValue();
379            }
380            comma();
381
382            final Node test = forNode.getTest();
383            property("test");
384            if (test != null) {
385                test.accept(this);
386            } else {
387                nullValue();
388            }
389            comma();
390
391            final Node update = forNode.getModify();
392            property("update");
393            if (update != null) {
394                update.accept(this);
395            } else {
396                nullValue();
397            }
398            comma();
399
400            property("body");
401            forNode.getBody().accept(this);
402        }
403
404        return leave();
405    }
406
407    @Override
408    public boolean enterFunctionNode(final FunctionNode functionNode) {
409        final boolean program = functionNode.isProgram();
410        if (program) {
411            return emitProgram(functionNode);
412        }
413
414        enterDefault(functionNode);
415        final String name;
416        if (functionNode.isDeclared()) {
417            name = "FunctionDeclaration";
418        } else {
419            name = "FunctionExpression";
420        }
421        type(name);
422        comma();
423
424        property("id");
425        final FunctionNode.Kind kind = functionNode.getKind();
426        if (functionNode.isAnonymous() || kind == FunctionNode.Kind.GETTER || kind == FunctionNode.Kind.SETTER) {
427            nullValue();
428        } else {
429            functionNode.getIdent().accept(this);
430        }
431        comma();
432
433        array("params", functionNode.getParameters());
434        comma();
435
436        arrayStart("defaults");
437        arrayEnd();
438        comma();
439
440        property("rest");
441        nullValue();
442        comma();
443
444        property("body");
445        functionNode.getBody().accept(this);
446        comma();
447
448        property("generator", false);
449        comma();
450
451        property("expression", false);
452
453        return leave();
454    }
455
456    private boolean emitProgram(final FunctionNode functionNode) {
457        enterDefault(functionNode);
458        type("Program");
459        comma();
460
461        // body consists of nested functions and statements
462        final List<Statement> stats = functionNode.getBody().getStatements();
463        final int size = stats.size();
464        int idx = 0;
465        arrayStart("body");
466
467        for (final Node stat : stats) {
468            stat.accept(this);
469            if (idx != (size - 1)) {
470                comma();
471            }
472            idx++;
473        }
474        arrayEnd();
475
476        return leave();
477    }
478
479    @Override
480    public boolean enterIdentNode(final IdentNode identNode) {
481        enterDefault(identNode);
482
483        final String name = identNode.getName();
484        if ("this".equals(name)) {
485            type("ThisExpression");
486        } else {
487            type("Identifier");
488            comma();
489            property("name", identNode.getName());
490        }
491
492        return leave();
493    }
494
495    @Override
496    public boolean enterIfNode(final IfNode ifNode) {
497        enterDefault(ifNode);
498
499        type("IfStatement");
500        comma();
501
502        property("test");
503        ifNode.getTest().accept(this);
504        comma();
505
506        property("consequent");
507        ifNode.getPass().accept(this);
508        final Node elsePart = ifNode.getFail();
509        comma();
510
511        property("alternate");
512        if (elsePart != null) {
513            elsePart.accept(this);
514        } else {
515            nullValue();
516        }
517
518        return leave();
519    }
520
521    @Override
522    public boolean enterIndexNode(final IndexNode indexNode) {
523        enterDefault(indexNode);
524
525        type("MemberExpression");
526        comma();
527
528        property("object");
529        indexNode.getBase().accept(this);
530        comma();
531
532        property("property");
533        indexNode.getIndex().accept(this);
534        comma();
535
536        property("computed", true);
537
538        return leave();
539    }
540
541    @Override
542    public boolean enterLabelNode(final LabelNode labelNode) {
543        enterDefault(labelNode);
544
545        type("LabeledStatement");
546        comma();
547
548        property("label", labelNode.getLabelName());
549        comma();
550
551        property("body");
552        labelNode.getBody().accept(this);
553
554        return leave();
555    }
556
557    @SuppressWarnings("rawtypes")
558    @Override
559    public boolean enterLiteralNode(final LiteralNode literalNode) {
560        enterDefault(literalNode);
561
562        if (literalNode instanceof LiteralNode.ArrayLiteralNode) {
563            type("ArrayExpression");
564            comma();
565
566            array("elements", ((LiteralNode.ArrayLiteralNode)literalNode).getElementExpressions());
567        } else {
568            type("Literal");
569            comma();
570
571            property("value");
572            final Object value = literalNode.getValue();
573            if (value instanceof RegexToken) {
574                // encode RegExp literals as Strings of the form /.../<flags>
575                final RegexToken regex = (RegexToken)value;
576                final StringBuilder regexBuf = new StringBuilder();
577                regexBuf.append('/');
578                regexBuf.append(regex.getExpression());
579                regexBuf.append('/');
580                regexBuf.append(regex.getOptions());
581                buf.append(quote(regexBuf.toString()));
582            } else {
583                final String str = literalNode.getString();
584                // encode every String literal with prefix '$' so that script
585                // can differentiate b/w RegExps as Strings and Strings.
586                buf.append(literalNode.isString()? quote("$" + str) : str);
587            }
588        }
589
590        return leave();
591    }
592
593    @Override
594    public boolean enterObjectNode(final ObjectNode objectNode) {
595        enterDefault(objectNode);
596
597        type("ObjectExpression");
598        comma();
599
600        array("properties", objectNode.getElements());
601
602        return leave();
603    }
604
605    @Override
606    public boolean enterPropertyNode(final PropertyNode propertyNode) {
607        final Node key = propertyNode.getKey();
608
609        final Node value = propertyNode.getValue();
610        if (value != null) {
611            objectStart();
612            location(propertyNode);
613
614            property("key");
615            key.accept(this);
616            comma();
617
618            property("value");
619            value.accept(this);
620            comma();
621
622            property("kind", "init");
623
624            objectEnd();
625        } else {
626            // getter
627            final Node getter = propertyNode.getGetter();
628            if (getter != null) {
629                objectStart();
630                location(propertyNode);
631
632                property("key");
633                key.accept(this);
634                comma();
635
636                property("value");
637                getter.accept(this);
638                comma();
639
640                property("kind", "get");
641
642                objectEnd();
643            }
644
645            // setter
646            final Node setter = propertyNode.getSetter();
647            if (setter != null) {
648                if (getter != null) {
649                    comma();
650                }
651                objectStart();
652                location(propertyNode);
653
654                property("key");
655                key.accept(this);
656                comma();
657
658                property("value");
659                setter.accept(this);
660                comma();
661
662                property("kind", "set");
663
664                objectEnd();
665            }
666        }
667
668        return false;
669    }
670
671    @Override
672    public boolean enterReturnNode(final ReturnNode returnNode) {
673        enterDefault(returnNode);
674
675        type("ReturnStatement");
676        comma();
677
678        final Node arg = returnNode.getExpression();
679        property("argument");
680        if (arg != null) {
681            arg.accept(this);
682        } else {
683            nullValue();
684        }
685
686        return leave();
687    }
688
689    @Override
690    public boolean enterRuntimeNode(final RuntimeNode runtimeNode) {
691        assert false : "should not reach here: RuntimeNode";
692        return false;
693    }
694
695    @Override
696    public boolean enterSplitNode(final SplitNode splitNode) {
697        assert false : "should not reach here: SplitNode";
698        return false;
699    }
700
701    @Override
702    public boolean enterSwitchNode(final SwitchNode switchNode) {
703        enterDefault(switchNode);
704
705        type("SwitchStatement");
706        comma();
707
708        property("discriminant");
709        switchNode.getExpression().accept(this);
710        comma();
711
712        array("cases", switchNode.getCases());
713
714        return leave();
715    }
716
717    @Override
718    public boolean enterTernaryNode(final TernaryNode ternaryNode) {
719        enterDefault(ternaryNode);
720
721        type("ConditionalExpression");
722        comma();
723
724        property("test");
725        ternaryNode.getTest().accept(this);
726        comma();
727
728        property("consequent");
729        ternaryNode.getTrueExpression().accept(this);
730        comma();
731
732        property("alternate");
733        ternaryNode.getFalseExpression().accept(this);
734
735        return leave();
736    }
737
738    @Override
739    public boolean enterThrowNode(final ThrowNode throwNode) {
740        enterDefault(throwNode);
741
742        type("ThrowStatement");
743        comma();
744
745        property("argument");
746        throwNode.getExpression().accept(this);
747
748        return leave();
749    }
750
751    @Override
752    public boolean enterTryNode(final TryNode tryNode) {
753        enterDefault(tryNode);
754
755        type("TryStatement");
756        comma();
757
758        property("block");
759        tryNode.getBody().accept(this);
760        comma();
761
762
763        final List<? extends Node> catches = tryNode.getCatches();
764        final List<CatchNode> guarded = new ArrayList<>();
765        CatchNode unguarded = null;
766        if (catches != null) {
767            for (final Node n : catches) {
768                final CatchNode cn = (CatchNode)n;
769                if (cn.getExceptionCondition() != null) {
770                    guarded.add(cn);
771                } else {
772                    assert unguarded == null: "too many unguarded?";
773                    unguarded = cn;
774                }
775            }
776        }
777
778        array("guardedHandlers", guarded);
779        comma();
780
781        property("handler");
782        if (unguarded != null) {
783            unguarded.accept(this);
784        } else {
785            nullValue();
786        }
787        comma();
788
789        property("finalizer");
790        final Node finallyNode = tryNode.getFinallyBody();
791        if (finallyNode != null) {
792            finallyNode.accept(this);
793        } else {
794            nullValue();
795        }
796
797        return leave();
798    }
799
800    @Override
801    public boolean enterUnaryNode(final UnaryNode unaryNode) {
802        enterDefault(unaryNode);
803
804        final TokenType tokenType = unaryNode.tokenType();
805        if (tokenType == TokenType.NEW) {
806            type("NewExpression");
807            comma();
808
809            final CallNode callNode = (CallNode)unaryNode.getExpression();
810            property("callee");
811            callNode.getFunction().accept(this);
812            comma();
813
814            array("arguments", callNode.getArgs());
815        } else {
816            final String operator;
817            final boolean prefix;
818            switch (tokenType) {
819            case INCPOSTFIX:
820                prefix = false;
821                operator = "++";
822                break;
823            case DECPOSTFIX:
824                prefix = false;
825                operator = "--";
826                break;
827            case INCPREFIX:
828                operator = "++";
829                prefix = true;
830                break;
831            case DECPREFIX:
832                operator = "--";
833                prefix = true;
834                break;
835            default:
836                prefix = true;
837                operator = tokenType.getName();
838                break;
839            }
840
841            type(unaryNode.isAssignment()? "UpdateExpression" : "UnaryExpression");
842            comma();
843
844            property("operator", operator);
845            comma();
846
847            property("prefix", prefix);
848            comma();
849
850            property("argument");
851            unaryNode.getExpression().accept(this);
852        }
853
854        return leave();
855    }
856
857    @Override
858    public boolean enterVarNode(final VarNode varNode) {
859        final Node init = varNode.getInit();
860        if (init instanceof FunctionNode && ((FunctionNode)init).isDeclared()) {
861            // function declaration - don't emit VariableDeclaration instead
862            // just emit FunctionDeclaration using 'init' Node.
863            init.accept(this);
864            return false;
865        }
866
867        enterDefault(varNode);
868
869        type("VariableDeclaration");
870        comma();
871
872        arrayStart("declarations");
873
874        // VariableDeclarator
875        objectStart();
876        location(varNode.getName());
877
878        type("VariableDeclarator");
879        comma();
880
881        property("id");
882        varNode.getName().accept(this);
883        comma();
884
885        property("init");
886        if (init != null) {
887            init.accept(this);
888        } else {
889            nullValue();
890        }
891
892        // VariableDeclarator
893        objectEnd();
894
895        // declarations
896        arrayEnd();
897
898        return leave();
899    }
900
901    @Override
902    public boolean enterWhileNode(final WhileNode whileNode) {
903        enterDefault(whileNode);
904
905        type(whileNode.isDoWhile() ? "DoWhileStatement" : "WhileStatement");
906        comma();
907
908        if (whileNode.isDoWhile()) {
909            property("body");
910            whileNode.getBody().accept(this);
911            comma();
912
913            property("test");
914            whileNode.getTest().accept(this);
915        } else {
916            property("test");
917            whileNode.getTest().accept(this);
918            comma();
919
920            property("body");
921            whileNode.getBody().accept(this);
922        }
923
924        return leave();
925    }
926
927    @Override
928    public boolean enterWithNode(final WithNode withNode) {
929        enterDefault(withNode);
930
931        type("WithStatement");
932        comma();
933
934        property("object");
935        withNode.getExpression().accept(this);
936        comma();
937
938        property("body");
939        withNode.getBody().accept(this);
940
941        return leave();
942   }
943
944    // Internals below
945
946    private JSONWriter(final boolean includeLocation) {
947        this.buf             = new StringBuilder();
948        this.includeLocation = includeLocation;
949    }
950
951    private final StringBuilder buf;
952    private final boolean includeLocation;
953
954    private String getString() {
955        return buf.toString();
956    }
957
958    private void property(final String key, final String value, final boolean escape) {
959        buf.append('"');
960        buf.append(key);
961        buf.append("\":");
962        if (value != null) {
963            if (escape) {
964                buf.append('"');
965            }
966            buf.append(value);
967            if (escape) {
968                buf.append('"');
969            }
970        }
971    }
972
973    private void property(final String key, final String value) {
974        property(key, value, true);
975    }
976
977    private void property(final String key, final boolean value) {
978        property(key, Boolean.toString(value), false);
979    }
980
981    private void property(final String key, final int value) {
982        property(key, Integer.toString(value), false);
983    }
984
985    private void property(final String key) {
986        property(key, null);
987    }
988
989    private void type(final String value) {
990        property("type", value);
991    }
992
993    private void objectStart(final String name) {
994        buf.append('"');
995        buf.append(name);
996        buf.append("\":{");
997    }
998
999    private void objectStart() {
1000        buf.append('{');
1001    }
1002
1003    private void objectEnd() {
1004        buf.append('}');
1005    }
1006
1007    private void array(final String name, final List<? extends Node> nodes) {
1008        // The size, idx comparison is just to avoid trailing comma..
1009        final int size = nodes.size();
1010        int idx = 0;
1011        arrayStart(name);
1012        for (final Node node : nodes) {
1013            if (node != null) {
1014                node.accept(this);
1015            } else {
1016                nullValue();
1017            }
1018            if (idx != (size - 1)) {
1019                comma();
1020            }
1021            idx++;
1022        }
1023        arrayEnd();
1024    }
1025
1026    private void arrayStart(final String name) {
1027        buf.append('"');
1028        buf.append(name);
1029        buf.append('"');
1030        buf.append(':');
1031        buf.append('[');
1032    }
1033
1034    private void arrayEnd() {
1035        buf.append(']');
1036    }
1037
1038    private void comma() {
1039        buf.append(',');
1040    }
1041
1042    private void nullValue() {
1043        buf.append("null");
1044    }
1045
1046    private void location(final Node node) {
1047        if (includeLocation) {
1048            objectStart("loc");
1049
1050            // source name
1051            final Source src = lc.getCurrentFunction().getSource();
1052            property("source", src.getName());
1053            comma();
1054
1055            // start position
1056            objectStart("start");
1057            final int start = node.getStart();
1058            property("line", src.getLine(start));
1059            comma();
1060            property("column", src.getColumn(start));
1061            objectEnd();
1062            comma();
1063
1064            // end position
1065            objectStart("end");
1066            final int end = node.getFinish();
1067            property("line", src.getLine(end));
1068            comma();
1069            property("column", src.getColumn(end));
1070            objectEnd();
1071
1072            // end 'loc'
1073            objectEnd();
1074
1075            comma();
1076        }
1077    }
1078
1079    private static String quote(final String str) {
1080        return JSONParser.quote(str);
1081    }
1082}
1083