JSONWriter.java revision 953:221a84ef44c0
1164426Ssam/*
2177505Ssam * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
3164426Ssam * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4164426Ssam *
5164426Ssam * This code is free software; you can redistribute it and/or modify it
6164426Ssam * under the terms of the GNU General Public License version 2 only, as
7164426Ssam * published by the Free Software Foundation.  Oracle designates this
8164426Ssam * particular file as subject to the "Classpath" exception as provided
9164426Ssam * by Oracle in the LICENSE file that accompanied this code.
10164426Ssam *
11164426Ssam * This code is distributed in the hope that it will be useful, but WITHOUT
12164426Ssam * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13164426Ssam * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14164426Ssam * version 2 for more details (a copy is included in the LICENSE file that
15164426Ssam * accompanied this code).
16164426Ssam *
17164426Ssam * You should have received a copy of the GNU General Public License version
18164426Ssam * 2 along with this work; if not, write to the Free Software Foundation,
19164426Ssam * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20164426Ssam *
21164426Ssam * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22164426Ssam * or visit www.oracle.com if you need additional information or have any
23164426Ssam * questions.
24164426Ssam */
25164426Ssam
26164426Ssampackage jdk.nashorn.internal.ir.debug;
27164426Ssam
28164426Ssamimport static jdk.nashorn.internal.runtime.Source.sourceFor;
29164426Ssam
30164426Ssamimport java.util.ArrayList;
31164426Ssamimport java.util.Arrays;
32164426Ssamimport java.util.List;
33164426Ssamimport jdk.nashorn.internal.ir.AccessNode;
34164426Ssamimport jdk.nashorn.internal.ir.BinaryNode;
35164426Ssamimport jdk.nashorn.internal.ir.Block;
36164426Ssamimport jdk.nashorn.internal.ir.BlockStatement;
37164426Ssamimport jdk.nashorn.internal.ir.BreakNode;
38164426Ssamimport jdk.nashorn.internal.ir.CallNode;
39164426Ssamimport jdk.nashorn.internal.ir.CaseNode;
40164426Ssamimport jdk.nashorn.internal.ir.CatchNode;
41164426Ssamimport jdk.nashorn.internal.ir.ContinueNode;
42164426Ssamimport jdk.nashorn.internal.ir.EmptyNode;
43164426Ssamimport jdk.nashorn.internal.ir.Expression;
44164426Ssamimport jdk.nashorn.internal.ir.ExpressionStatement;
45164426Ssamimport jdk.nashorn.internal.ir.ForNode;
46164426Ssamimport jdk.nashorn.internal.ir.FunctionNode;
47164426Ssamimport jdk.nashorn.internal.ir.IdentNode;
48164426Ssamimport jdk.nashorn.internal.ir.IfNode;
49164426Ssamimport jdk.nashorn.internal.ir.IndexNode;
50164426Ssamimport jdk.nashorn.internal.ir.JoinPredecessorExpression;
51164426Ssamimport jdk.nashorn.internal.ir.LabelNode;
52164426Ssamimport jdk.nashorn.internal.ir.LexicalContext;
53164426Ssamimport jdk.nashorn.internal.ir.LiteralNode;
54164426Ssamimport jdk.nashorn.internal.ir.Node;
55164426Ssamimport jdk.nashorn.internal.ir.ObjectNode;
56164426Ssamimport jdk.nashorn.internal.ir.PropertyNode;
57164426Ssamimport jdk.nashorn.internal.ir.ReturnNode;
58164426Ssamimport jdk.nashorn.internal.ir.RuntimeNode;
59164426Ssamimport jdk.nashorn.internal.ir.SplitNode;
60164426Ssamimport jdk.nashorn.internal.ir.Statement;
61164426Ssamimport jdk.nashorn.internal.ir.SwitchNode;
62164426Ssamimport jdk.nashorn.internal.ir.TernaryNode;
63164426Ssamimport jdk.nashorn.internal.ir.ThrowNode;
64164426Ssamimport jdk.nashorn.internal.ir.TryNode;
65164426Ssamimport jdk.nashorn.internal.ir.UnaryNode;
66164426Ssamimport jdk.nashorn.internal.ir.VarNode;
67164426Ssamimport jdk.nashorn.internal.ir.WhileNode;
68259342Sianimport jdk.nashorn.internal.ir.WithNode;
69164426Ssamimport jdk.nashorn.internal.ir.visitor.NodeVisitor;
70164426Ssamimport jdk.nashorn.internal.parser.JSONParser;
71164426Ssamimport jdk.nashorn.internal.parser.Lexer.RegexToken;
72164426Ssamimport jdk.nashorn.internal.parser.Parser;
73164426Ssamimport jdk.nashorn.internal.parser.TokenType;
74164426Ssamimport jdk.nashorn.internal.runtime.Context;
75164426Ssamimport jdk.nashorn.internal.runtime.ParserException;
76164426Ssamimport jdk.nashorn.internal.runtime.Source;
77164426Ssam
78164426Ssam/**
79164426Ssam * This IR writer produces a JSON string that represents AST as a JSON string.
80164426Ssam */
81164426Ssampublic final class JSONWriter extends NodeVisitor<LexicalContext> {
82164426Ssam
83164426Ssam    /**
84164426Ssam     * Returns AST as JSON compatible string.
85164426Ssam     *
86164426Ssam     * @param context context
87164426Ssam     * @param code code to be parsed
88164426Ssam     * @param name name of the code source (used for location)
89186352Ssam     * @param includeLoc tells whether to include location information for nodes or not
90186352Ssam     * @return JSON string representation of AST of the supplied code
91164426Ssam     */
92164426Ssam    public static String parse(final Context context, final String code, final String name, final boolean includeLoc) {
93236987Simp        final Parser       parser     = new Parser(context.getEnv(), sourceFor(name, code), new Context.ThrowErrorManager(), context.getEnv()._strict, context.getLogger(Parser.class));
94236987Simp        final JSONWriter   jsonWriter = new JSONWriter(includeLoc);
95166064Scognet        try {
96166064Scognet            final FunctionNode functionNode = parser.parse(); //symbol name is ":program", default
97166064Scognet            functionNode.accept(jsonWriter);
98166064Scognet            return jsonWriter.getString();
99164426Ssam        } catch (final ParserException e) {
100164426Ssam            e.throwAsEcmaException();
101164426Ssam            return null;
102164426Ssam        }
103164426Ssam    }
104164426Ssam
105164426Ssam    @Override
106164426Ssam    public boolean enterJoinPredecessorExpression(final JoinPredecessorExpression joinPredecessorExpression) {
107164426Ssam        final Expression expr = joinPredecessorExpression.getExpression();
108164426Ssam        if(expr != null) {
109164426Ssam            expr.accept(this);
110164426Ssam        } else {
111164426Ssam            nullValue();
112164426Ssam        }
113164426Ssam        return false;
114164426Ssam    }
115164426Ssam
116164426Ssam    @Override
117164426Ssam    protected boolean enterDefault(final Node node) {
118164426Ssam        objectStart();
119164426Ssam        location(node);
120164426Ssam
121164426Ssam        return true;
122164426Ssam    }
123164426Ssam
124164426Ssam    private boolean leave() {
125164426Ssam        objectEnd();
126164426Ssam        return false;
127186352Ssam    }
128164426Ssam
129164426Ssam    @Override
130164426Ssam    protected Node leaveDefault(final Node node) {
131164426Ssam        objectEnd();
132166339Skevlo        return null;
133164426Ssam    }
134164426Ssam
135164426Ssam    @Override
136164426Ssam    public boolean enterAccessNode(final AccessNode accessNode) {
137164426Ssam        enterDefault(accessNode);
138164426Ssam
139164426Ssam        type("MemberExpression");
140164426Ssam        comma();
141164426Ssam
142164426Ssam        property("object");
143164426Ssam        accessNode.getBase().accept(this);
144164426Ssam        comma();
145164426Ssam
146192660Ssam        property("property", accessNode.getProperty());
147164426Ssam        comma();
148164426Ssam
149164426Ssam        property("computed", false);
150186352Ssam
151164426Ssam        return leave();
152164426Ssam    }
153164426Ssam
154164426Ssam    @Override
155164426Ssam    public boolean enterBlock(final Block block) {
156194321Ssam        enterDefault(block);
157194321Ssam
158194321Ssam        type("BlockStatement");
159164426Ssam        comma();
160164426Ssam
161186352Ssam        array("body", block.getStatements());
162164426Ssam
163177505Ssam        return leave();
164164426Ssam    }
165164426Ssam
166164426Ssam    @Override
167164426Ssam    public boolean enterBinaryNode(final BinaryNode binaryNode) {
168186352Ssam        enterDefault(binaryNode);
169186352Ssam
170186352Ssam        final String name;
171186352Ssam        if (binaryNode.isAssignment()) {
172186352Ssam            name = "AssignmentExpression";
173186352Ssam        } else if (binaryNode.isLogical()) {
174186352Ssam            name = "LogicalExpression";
175186352Ssam        } else {
176186352Ssam            name = "BinaryExpression";
177186352Ssam        }
178186352Ssam
179186352Ssam        type(name);
180194321Ssam        comma();
181177505Ssam
182164426Ssam        property("operator", binaryNode.tokenType().getName());
183164426Ssam        comma();
184164426Ssam
185164426Ssam        property("left");
186164426Ssam        binaryNode.lhs().accept(this);
187186352Ssam        comma();
188186352Ssam
189194321Ssam        property("right");
190177505Ssam        binaryNode.rhs().accept(this);
191164426Ssam
192164426Ssam        return leave();
193164426Ssam    }
194164426Ssam
195164426Ssam    @Override
196164426Ssam    public boolean enterBreakNode(final BreakNode breakNode) {
197164426Ssam        enterDefault(breakNode);
198164426Ssam
199164426Ssam        type("BreakStatement");
200164426Ssam        comma();
201164426Ssam
202164426Ssam        final String label = breakNode.getLabelName();
203164426Ssam        if(label != null) {
204164426Ssam            property("label", label);
205164426Ssam        } else {
206164426Ssam            property("label");
207164426Ssam            nullValue();
208164426Ssam        }
209164426Ssam
210164426Ssam        return leave();
211164426Ssam    }
212164426Ssam
213164426Ssam    @Override
214164426Ssam    public boolean enterCallNode(final CallNode callNode) {
215164426Ssam        enterDefault(callNode);
216164426Ssam
217164426Ssam        type("CallExpression");
218164426Ssam        comma();
219164426Ssam
220164426Ssam        property("callee");
221164426Ssam        callNode.getFunction().accept(this);
222186352Ssam        comma();
223164426Ssam
224164426Ssam        array("arguments", callNode.getArgs());
225164426Ssam
226164426Ssam        return leave();
227164426Ssam    }
228164426Ssam
229164426Ssam    @Override
230164426Ssam    public boolean enterCaseNode(final CaseNode caseNode) {
231164426Ssam        enterDefault(caseNode);
232193096Sattilio
233164426Ssam        type("SwitchCase");
234164426Ssam        comma();
235164426Ssam
236164426Ssam        final Node test = caseNode.getTest();
237166339Skevlo        property("test");
238164426Ssam        if (test != null) {
239164426Ssam            test.accept(this);
240164426Ssam        } else {
241164426Ssam            nullValue();
242194321Ssam        }
243186352Ssam        comma();
244164426Ssam
245164426Ssam        array("consequent", caseNode.getBody().getStatements());
246164426Ssam
247164426Ssam        return leave();
248164426Ssam    }
249164426Ssam
250164426Ssam    @Override
251164426Ssam    public boolean enterCatchNode(final CatchNode catchNode) {
252164426Ssam        enterDefault(catchNode);
253164426Ssam
254227309Sed        type("CatchClause");
255227309Sed        comma();
256164426Ssam
257164426Ssam        property("param");
258164426Ssam        catchNode.getException().accept(this);
259186352Ssam        comma();
260186420Ssam
261164426Ssam        final Node guard = catchNode.getExceptionCondition();
262164426Ssam        if (guard != null) {
263164426Ssam            property("guard");
264164426Ssam            guard.accept(this);
265164426Ssam            comma();
266164426Ssam        }
267164426Ssam
268164426Ssam        property("body");
269164426Ssam        catchNode.getBody().accept(this);
270164426Ssam
271164426Ssam        return leave();
272164426Ssam    }
273164426Ssam
274164426Ssam    @Override
275164426Ssam    public boolean enterContinueNode(final ContinueNode continueNode) {
276164426Ssam        enterDefault(continueNode);
277164426Ssam
278164426Ssam        type("ContinueStatement");
279164426Ssam        comma();
280164426Ssam
281164426Ssam        final String label = continueNode.getLabelName();
282186352Ssam        if(label != null) {
283186352Ssam            property("label", label);
284186352Ssam        } else {
285186352Ssam            property("label");
286186352Ssam            nullValue();
287186352Ssam        }
288186352Ssam
289186352Ssam        return leave();
290186352Ssam    }
291186352Ssam
292186352Ssam    @Override
293186352Ssam    public boolean enterEmptyNode(final EmptyNode emptyNode) {
294186352Ssam        enterDefault(emptyNode);
295186352Ssam
296164426Ssam        type("EmptyStatement");
297164426Ssam
298186352Ssam        return leave();
299186352Ssam    }
300186352Ssam
301186352Ssam    @Override
302186352Ssam    public boolean enterExpressionStatement(final ExpressionStatement expressionStatement) {
303186420Ssam        // handle debugger statement
304186352Ssam        final Node expression = expressionStatement.getExpression();
305164426Ssam        if (expression instanceof RuntimeNode) {
306236987Simp            expression.accept(this);
307186420Ssam            return false;
308186420Ssam        }
309186420Ssam
310186420Ssam        enterDefault(expressionStatement);
311186352Ssam
312186352Ssam        type("ExpressionStatement");
313186420Ssam        comma();
314186352Ssam
315186420Ssam        property("expression");
316164426Ssam        expression.accept(this);
317164426Ssam
318186352Ssam        return leave();
319164426Ssam    }
320164426Ssam
321164426Ssam    @Override
322164426Ssam    public boolean enterBlockStatement(final BlockStatement blockStatement) {
323164426Ssam        enterDefault(blockStatement);
324164426Ssam
325164426Ssam        type("BlockStatement");
326164426Ssam        comma();
327164426Ssam
328164426Ssam        property("block");
329186352Ssam        blockStatement.getBlock().accept(this);
330164426Ssam
331164426Ssam        return leave();
332164426Ssam    }
333164426Ssam
334164426Ssam    @Override
335164426Ssam    public boolean enterForNode(final ForNode forNode) {
336164426Ssam        enterDefault(forNode);
337164426Ssam
338164426Ssam        if (forNode.isForIn() || (forNode.isForEach() && forNode.getInit() != null)) {
339164426Ssam            type("ForInStatement");
340186352Ssam            comma();
341186352Ssam
342186352Ssam            final Node init = forNode.getInit();
343164426Ssam            assert init != null;
344164426Ssam            property("left");
345164426Ssam            init.accept(this);
346186352Ssam            comma();
347186352Ssam
348164426Ssam            final Node modify = forNode.getModify();
349164426Ssam            assert modify != null;
350186352Ssam            property("right");
351186352Ssam            modify.accept(this);
352164426Ssam            comma();
353186352Ssam
354164426Ssam            property("body");
355164426Ssam            forNode.getBody().accept(this);
356164426Ssam            comma();
357164426Ssam
358164426Ssam            property("each", forNode.isForEach());
359164426Ssam        } else {
360164426Ssam            type("ForStatement");
361164426Ssam            comma();
362164426Ssam
363164426Ssam            final Node init = forNode.getInit();
364207554Ssobomax            property("init");
365164426Ssam            if (init != null) {
366164426Ssam                init.accept(this);
367164426Ssam            } else {
368164426Ssam                nullValue();
369189645Ssam            }
370189645Ssam            comma();
371189645Ssam
372164426Ssam            final Node test = forNode.getTest();
373164426Ssam            property("test");
374164426Ssam            if (test != null) {
375164426Ssam                test.accept(this);
376164426Ssam            } else {
377164426Ssam                nullValue();
378164426Ssam            }
379164426Ssam            comma();
380192660Ssam
381192660Ssam            final Node update = forNode.getModify();
382164426Ssam            property("update");
383164426Ssam            if (update != null) {
384164426Ssam                update.accept(this);
385164426Ssam            } else {
386164426Ssam                nullValue();
387164426Ssam            }
388186352Ssam            comma();
389186352Ssam
390164426Ssam            property("body");
391164426Ssam            forNode.getBody().accept(this);
392164426Ssam        }
393164426Ssam
394164426Ssam        return leave();
395164426Ssam    }
396164426Ssam
397164426Ssam    @Override
398164426Ssam    public boolean enterFunctionNode(final FunctionNode functionNode) {
399164426Ssam        final boolean program = functionNode.isProgram();
400164426Ssam        if (program) {
401164426Ssam            return emitProgram(functionNode);
402164426Ssam        }
403164426Ssam
404164426Ssam        enterDefault(functionNode);
405164426Ssam        final String name;
406164426Ssam        if (functionNode.isDeclared()) {
407164426Ssam            name = "FunctionDeclaration";
408164426Ssam        } else {
409164426Ssam            name = "FunctionExpression";
410164426Ssam        }
411164426Ssam        type(name);
412164426Ssam        comma();
413164426Ssam
414164426Ssam        property("id");
415164426Ssam        final FunctionNode.Kind kind = functionNode.getKind();
416164426Ssam        if (functionNode.isAnonymous() || kind == FunctionNode.Kind.GETTER || kind == FunctionNode.Kind.SETTER) {
417164426Ssam            nullValue();
418164426Ssam        } else {
419164426Ssam            functionNode.getIdent().accept(this);
420164426Ssam        }
421164426Ssam        comma();
422164426Ssam
423164426Ssam        array("params", functionNode.getParameters());
424164426Ssam        comma();
425164426Ssam
426164426Ssam        arrayStart("defaults");
427164426Ssam        arrayEnd();
428164426Ssam        comma();
429164426Ssam
430164426Ssam        property("rest");
431164426Ssam        nullValue();
432164426Ssam        comma();
433164426Ssam
434164426Ssam        property("body");
435164426Ssam        functionNode.getBody().accept(this);
436164426Ssam        comma();
437164426Ssam
438164426Ssam        property("generator", false);
439195049Srwatson        comma();
440164426Ssam
441164426Ssam        property("expression", false);
442164426Ssam
443164426Ssam        return leave();
444164426Ssam    }
445164426Ssam
446164426Ssam    private boolean emitProgram(final FunctionNode functionNode) {
447164426Ssam        enterDefault(functionNode);
448164426Ssam        type("Program");
449195049Srwatson        comma();
450164426Ssam
451164426Ssam        // body consists of nested functions and statements
452164426Ssam        final List<Statement> stats = functionNode.getBody().getStatements();
453164426Ssam        final int size = stats.size();
454164426Ssam        int idx = 0;
455164426Ssam        arrayStart("body");
456164426Ssam
457164426Ssam        for (final Node stat : stats) {
458164426Ssam            stat.accept(this);
459164426Ssam            if (idx != (size - 1)) {
460164426Ssam                comma();
461164426Ssam            }
462164426Ssam            idx++;
463164426Ssam        }
464164426Ssam        arrayEnd();
465164426Ssam
466164426Ssam        return leave();
467164426Ssam    }
468164426Ssam
469164426Ssam    @Override
470164426Ssam    public boolean enterIdentNode(final IdentNode identNode) {
471164426Ssam        enterDefault(identNode);
472164426Ssam
473164426Ssam        final String name = identNode.getName();
474164426Ssam        if ("this".equals(name)) {
475164426Ssam            type("ThisExpression");
476164426Ssam        } else {
477164426Ssam            type("Identifier");
478164426Ssam            comma();
479164426Ssam            property("name", identNode.getName());
480164426Ssam        }
481164426Ssam
482164426Ssam        return leave();
483183886Ssam    }
484164426Ssam
485164426Ssam    @Override
486164426Ssam    public boolean enterIfNode(final IfNode ifNode) {
487164426Ssam        enterDefault(ifNode);
488164426Ssam
489166064Scognet        type("IfStatement");
490164426Ssam        comma();
491164426Ssam
492164426Ssam        property("test");
493164426Ssam        ifNode.getTest().accept(this);
494164426Ssam        comma();
495164426Ssam
496164426Ssam        property("consequent");
497164426Ssam        ifNode.getPass().accept(this);
498164426Ssam        final Node elsePart = ifNode.getFail();
499164426Ssam        comma();
500236987Simp
501164426Ssam        property("alternate");
502164426Ssam        if (elsePart != null) {
503164426Ssam            elsePart.accept(this);
504164426Ssam        } else {
505164426Ssam            nullValue();
506164426Ssam        }
507164426Ssam
508164426Ssam        return leave();
509164426Ssam    }
510164426Ssam
511164426Ssam    @Override
512164426Ssam    public boolean enterIndexNode(final IndexNode indexNode) {
513164426Ssam        enterDefault(indexNode);
514164426Ssam
515164426Ssam        type("MemberExpression");
516164426Ssam        comma();
517164426Ssam
518164426Ssam        property("object");
519164426Ssam        indexNode.getBase().accept(this);
520164426Ssam        comma();
521164426Ssam
522164426Ssam        property("property");
523164426Ssam        indexNode.getIndex().accept(this);
524164426Ssam        comma();
525164426Ssam
526164426Ssam        property("computed", true);
527164426Ssam
528164426Ssam        return leave();
529164426Ssam    }
530164426Ssam
531164426Ssam    @Override
532164426Ssam    public boolean enterLabelNode(final LabelNode labelNode) {
533164426Ssam        enterDefault(labelNode);
534164426Ssam
535164426Ssam        type("LabeledStatement");
536164426Ssam        comma();
537164426Ssam
538164426Ssam        property("label", labelNode.getLabelName());
539164426Ssam        comma();
540164426Ssam
541164426Ssam        property("body");
542164426Ssam        labelNode.getBody().accept(this);
543164426Ssam
544164426Ssam        return leave();
545164426Ssam    }
546164426Ssam
547164426Ssam    @SuppressWarnings("rawtypes")
548164426Ssam    @Override
549164426Ssam    public boolean enterLiteralNode(final LiteralNode literalNode) {
550164426Ssam        enterDefault(literalNode);
551164426Ssam
552164426Ssam        if (literalNode instanceof LiteralNode.ArrayLiteralNode) {
553164426Ssam            type("ArrayExpression");
554164426Ssam            comma();
555164426Ssam
556164426Ssam            final Node[] value = literalNode.getArray();
557164426Ssam            array("elements", Arrays.asList(value));
558164426Ssam        } else {
559164426Ssam            type("Literal");
560164426Ssam            comma();
561164426Ssam
562164426Ssam            property("value");
563164426Ssam            final Object value = literalNode.getValue();
564164426Ssam            if (value instanceof RegexToken) {
565164426Ssam                // encode RegExp literals as Strings of the form /.../<flags>
566164426Ssam                final RegexToken regex = (RegexToken)value;
567164426Ssam                final StringBuilder regexBuf = new StringBuilder();
568164426Ssam                regexBuf.append('/');
569164426Ssam                regexBuf.append(regex.getExpression());
570164426Ssam                regexBuf.append('/');
571164426Ssam                regexBuf.append(regex.getOptions());
572164426Ssam                buf.append(quote(regexBuf.toString()));
573164426Ssam            } else {
574164426Ssam                final String str = literalNode.getString();
575164426Ssam                // encode every String literal with prefix '$' so that script
576164426Ssam                // can differentiate b/w RegExps as Strings and Strings.
577164426Ssam                buf.append(literalNode.isString()? quote("$" + str) : str);
578164426Ssam            }
579164426Ssam        }
580164426Ssam
581186352Ssam        return leave();
582177505Ssam    }
583177505Ssam
584177505Ssam    @Override
585177505Ssam    public boolean enterObjectNode(final ObjectNode objectNode) {
586177505Ssam        enterDefault(objectNode);
587177505Ssam
588177505Ssam        type("ObjectExpression");
589177505Ssam        comma();
590177505Ssam
591186352Ssam        array("properties", objectNode.getElements());
592177505Ssam
593177505Ssam        return leave();
594177505Ssam    }
595177505Ssam
596186352Ssam    @Override
597186352Ssam    public boolean enterPropertyNode(final PropertyNode propertyNode) {
598186352Ssam        final Node key = propertyNode.getKey();
599177505Ssam
600177505Ssam        final Node value = propertyNode.getValue();
601177505Ssam        if (value != null) {
602177505Ssam            objectStart();
603177505Ssam            location(propertyNode);
604177505Ssam
605177505Ssam            property("key");
606177505Ssam            key.accept(this);
607177505Ssam            comma();
608177505Ssam
609177505Ssam            property("value");
610177505Ssam            value.accept(this);
611186352Ssam            comma();
612186352Ssam
613186352Ssam            property("kind", "init");
614186352Ssam
615186352Ssam            objectEnd();
616186352Ssam        } else {
617186352Ssam            // getter
618186352Ssam            final Node getter = propertyNode.getGetter();
619186352Ssam            if (getter != null) {
620186352Ssam                objectStart();
621186352Ssam                location(propertyNode);
622186352Ssam
623186352Ssam                property("key");
624186352Ssam                key.accept(this);
625186352Ssam                comma();
626186352Ssam
627186352Ssam                property("value");
628186352Ssam                getter.accept(this);
629186352Ssam                comma();
630186352Ssam
631186352Ssam                property("kind", "get");
632186352Ssam
633186352Ssam                objectEnd();
634186352Ssam            }
635177505Ssam
636177505Ssam            // setter
637177505Ssam            final Node setter = propertyNode.getSetter();
638177505Ssam            if (setter != null) {
639177505Ssam                if (getter != null) {
640177505Ssam                    comma();
641177505Ssam                }
642177505Ssam                objectStart();
643177505Ssam                location(propertyNode);
644177505Ssam
645177505Ssam                property("key");
646177505Ssam                key.accept(this);
647177505Ssam                comma();
648177505Ssam
649177505Ssam                property("value");
650177505Ssam                setter.accept(this);
651177505Ssam                comma();
652177505Ssam
653177505Ssam                property("kind", "set");
654177505Ssam
655186352Ssam                objectEnd();
656186352Ssam            }
657186352Ssam        }
658186352Ssam
659186352Ssam        return false;
660186352Ssam    }
661186352Ssam
662186352Ssam    @Override
663186352Ssam    public boolean enterReturnNode(final ReturnNode returnNode) {
664186352Ssam        enterDefault(returnNode);
665186352Ssam
666186352Ssam        type("ReturnStatement");
667186352Ssam        comma();
668164426Ssam
669164426Ssam        final Node arg = returnNode.getExpression();
670194321Ssam        property("argument");
671213893Smarius        if (arg != null) {
672164426Ssam            arg.accept(this);
673169954Ssam        } else {
674186352Ssam            nullValue();
675186352Ssam        }
676186352Ssam
677186352Ssam        return leave();
678186352Ssam    }
679186352Ssam
680186352Ssam    @Override
681186352Ssam    public boolean enterRuntimeNode(final RuntimeNode runtimeNode) {
682186352Ssam        final RuntimeNode.Request req = runtimeNode.getRequest();
683186352Ssam
684186352Ssam        if (req == RuntimeNode.Request.DEBUGGER) {
685186352Ssam            enterDefault(runtimeNode);
686186352Ssam            type("DebuggerStatement");
687186352Ssam            return leave();
688186352Ssam        }
689186352Ssam
690186352Ssam        return false;
691186352Ssam    }
692186352Ssam
693186352Ssam    @Override
694186352Ssam    public boolean enterSplitNode(final SplitNode splitNode) {
695186352Ssam        return false;
696213893Smarius    }
697213893Smarius
698186352Ssam    @Override
699186352Ssam    public boolean enterSwitchNode(final SwitchNode switchNode) {
700186352Ssam        enterDefault(switchNode);
701186352Ssam
702186352Ssam        type("SwitchStatement");
703186352Ssam        comma();
704186352Ssam
705186352Ssam        property("discriminant");
706186352Ssam        switchNode.getExpression().accept(this);
707186352Ssam        comma();
708186352Ssam
709186352Ssam        array("cases", switchNode.getCases());
710186352Ssam
711186352Ssam        return leave();
712186352Ssam    }
713186352Ssam
714186352Ssam    @Override
715186352Ssam    public boolean enterTernaryNode(final TernaryNode ternaryNode) {
716186420Ssam        enterDefault(ternaryNode);
717169954Ssam
718186420Ssam        type("ConditionalExpression");
719186420Ssam        comma();
720186420Ssam
721186420Ssam        property("test");
722169954Ssam        ternaryNode.getTest().accept(this);
723164426Ssam        comma();
724213893Smarius
725213893Smarius        property("consequent");
726213893Smarius        ternaryNode.getTrueExpression().accept(this);
727213893Smarius        comma();
728213893Smarius
729213893Smarius        property("alternate");
730177505Ssam        ternaryNode.getFalseExpression().accept(this);
731164426Ssam
732164426Ssam        return leave();
733164426Ssam    }
734164426Ssam
735164426Ssam    @Override
736164426Ssam    public boolean enterThrowNode(final ThrowNode throwNode) {
737164426Ssam        enterDefault(throwNode);
738164426Ssam
739164426Ssam        type("ThrowStatement");
740166064Scognet        comma();
741164426Ssam
742164426Ssam        property("argument");
743164426Ssam        throwNode.getExpression().accept(this);
744164426Ssam
745164426Ssam        return leave();
746164426Ssam    }
747164426Ssam
748164426Ssam    @Override
749164426Ssam    public boolean enterTryNode(final TryNode tryNode) {
750164426Ssam        enterDefault(tryNode);
751164426Ssam
752164426Ssam        type("TryStatement");
753164426Ssam        comma();
754164426Ssam
755164426Ssam        property("block");
756164426Ssam        tryNode.getBody().accept(this);
757164426Ssam        comma();
758164426Ssam
759164426Ssam
760164426Ssam        final List<? extends Node> catches = tryNode.getCatches();
761164426Ssam        final List<CatchNode> guarded = new ArrayList<>();
762164426Ssam        CatchNode unguarded = null;
763164426Ssam        if (catches != null) {
764164426Ssam            for (final Node n : catches) {
765164426Ssam                final CatchNode cn = (CatchNode)n;
766164426Ssam                if (cn.getExceptionCondition() != null) {
767164426Ssam                    guarded.add(cn);
768164426Ssam                } else {
769164426Ssam                    assert unguarded == null: "too many unguarded?";
770164426Ssam                    unguarded = cn;
771164426Ssam                }
772164426Ssam            }
773164426Ssam        }
774164426Ssam
775164426Ssam        array("guardedHandlers", guarded);
776164426Ssam        comma();
777164426Ssam
778164426Ssam        property("handler");
779164426Ssam        if (unguarded != null) {
780164426Ssam            unguarded.accept(this);
781186352Ssam        } else {
782164426Ssam            nullValue();
783193096Sattilio        }
784186352Ssam        comma();
785164426Ssam
786186352Ssam        property("finalizer");
787186352Ssam        final Node finallyNode = tryNode.getFinallyBody();
788186352Ssam        if (finallyNode != null) {
789186352Ssam            finallyNode.accept(this);
790186352Ssam        } else {
791164426Ssam            nullValue();
792164426Ssam        }
793164426Ssam
794186352Ssam        return leave();
795186352Ssam    }
796186352Ssam
797186352Ssam    @Override
798186352Ssam    public boolean enterUnaryNode(final UnaryNode unaryNode) {
799164426Ssam        enterDefault(unaryNode);
800164426Ssam
801164426Ssam        final TokenType tokenType = unaryNode.tokenType();
802164426Ssam        if (tokenType == TokenType.NEW) {
803164426Ssam            type("NewExpression");
804164426Ssam            comma();
805164426Ssam
806186352Ssam            final CallNode callNode = (CallNode)unaryNode.getExpression();
807186352Ssam            property("callee");
808186352Ssam            callNode.getFunction().accept(this);
809177505Ssam            comma();
810164426Ssam
811164426Ssam            array("arguments", callNode.getArgs());
812164426Ssam        } else {
813164426Ssam            final String operator;
814164426Ssam            final boolean prefix;
815164426Ssam            switch (tokenType) {
816164426Ssam            case INCPOSTFIX:
817164426Ssam                prefix = false;
818186352Ssam                operator = "++";
819164426Ssam                break;
820164426Ssam            case DECPOSTFIX:
821186352Ssam                prefix = false;
822164426Ssam                operator = "--";
823186352Ssam                break;
824186352Ssam            case INCPREFIX:
825164426Ssam                operator = "++";
826164426Ssam                prefix = true;
827164426Ssam                break;
828164426Ssam            case DECPREFIX:
829164426Ssam                operator = "--";
830164426Ssam                prefix = true;
831164426Ssam                break;
832164426Ssam            default:
833164426Ssam                prefix = true;
834164426Ssam                operator = tokenType.getName();
835186352Ssam                break;
836164426Ssam            }
837164426Ssam
838164426Ssam            type(unaryNode.isAssignment()? "UpdateExpression" : "UnaryExpression");
839164426Ssam            comma();
840164426Ssam
841164426Ssam            property("operator", operator);
842164426Ssam            comma();
843164426Ssam
844164426Ssam            property("prefix", prefix);
845164426Ssam            comma();
846164426Ssam
847164426Ssam            property("argument");
848164426Ssam            unaryNode.getExpression().accept(this);
849164426Ssam        }
850164426Ssam
851164426Ssam        return leave();
852164426Ssam    }
853164426Ssam
854164426Ssam    @Override
855164426Ssam    public boolean enterVarNode(final VarNode varNode) {
856164426Ssam        final Node init = varNode.getInit();
857164426Ssam        if (init instanceof FunctionNode && ((FunctionNode)init).isDeclared()) {
858164426Ssam            // function declaration - don't emit VariableDeclaration instead
859164426Ssam            // just emit FunctionDeclaration using 'init' Node.
860164426Ssam            init.accept(this);
861164426Ssam            return false;
862164426Ssam        }
863164426Ssam
864164426Ssam        enterDefault(varNode);
865164426Ssam
866164426Ssam        type("VariableDeclaration");
867164426Ssam        comma();
868164426Ssam
869164426Ssam        arrayStart("declarations");
870164426Ssam
871164426Ssam        // VariableDeclarator
872164426Ssam        objectStart();
873164426Ssam        location(varNode.getName());
874164426Ssam
875164426Ssam        type("VariableDeclarator");
876192660Ssam        comma();
877192660Ssam
878164426Ssam        property("id");
879164426Ssam        varNode.getName().accept(this);
880164426Ssam        comma();
881164426Ssam
882164426Ssam        property("init");
883192660Ssam        if (init != null) {
884192660Ssam            init.accept(this);
885192660Ssam        } else {
886192660Ssam            nullValue();
887192660Ssam        }
888192660Ssam
889192660Ssam        // VariableDeclarator
890192660Ssam        objectEnd();
891192660Ssam
892192660Ssam        // declarations
893192660Ssam        arrayEnd();
894164426Ssam
895164426Ssam        return leave();
896164426Ssam    }
897164426Ssam
898164426Ssam    @Override
899164426Ssam    public boolean enterWhileNode(final WhileNode whileNode) {
900164426Ssam        enterDefault(whileNode);
901192660Ssam
902192660Ssam        type(whileNode.isDoWhile() ? "DoWhileStatement" : "WhileStatement");
903192660Ssam        comma();
904164426Ssam
905164426Ssam        if (whileNode.isDoWhile()) {
906164426Ssam            property("body");
907164426Ssam            whileNode.getBody().accept(this);
908164426Ssam            comma();
909164426Ssam
910164426Ssam            property("test");
911164426Ssam            whileNode.getTest().accept(this);
912164426Ssam        } else {
913164426Ssam            property("test");
914164426Ssam            whileNode.getTest().accept(this);
915164426Ssam            comma();
916164426Ssam
917164426Ssam            property("body");
918164426Ssam            whileNode.getBody().accept(this);
919164426Ssam        }
920164426Ssam
921164426Ssam        return leave();
922164426Ssam    }
923164426Ssam
924164426Ssam    @Override
925192660Ssam    public boolean enterWithNode(final WithNode withNode) {
926164426Ssam        enterDefault(withNode);
927164426Ssam
928164426Ssam        type("WithStatement");
929164426Ssam        comma();
930164426Ssam
931164426Ssam        property("object");
932164426Ssam        withNode.getExpression().accept(this);
933164426Ssam        comma();
934164426Ssam
935164426Ssam        property("body");
936164426Ssam        withNode.getBody().accept(this);
937164426Ssam
938164426Ssam        return leave();
939164426Ssam   }
940164426Ssam
941164426Ssam    // Internals below
942164426Ssam
943164426Ssam    private JSONWriter(final boolean includeLocation) {
944164426Ssam        super(new LexicalContext());
945164426Ssam        this.buf             = new StringBuilder();
946164426Ssam        this.includeLocation = includeLocation;
947164426Ssam    }
948186352Ssam
949164426Ssam    private final StringBuilder buf;
950164426Ssam    private final boolean includeLocation;
951164426Ssam
952164426Ssam    private String getString() {
953164426Ssam        return buf.toString();
954164426Ssam    }
955164426Ssam
956166339Skevlo    private void property(final String key, final String value, final boolean escape) {
957166339Skevlo        buf.append('"');
958164426Ssam        buf.append(key);
959164426Ssam        buf.append("\":");
960164426Ssam        if (value != null) {
961164426Ssam            if (escape) {
962164426Ssam                buf.append('"');
963164426Ssam            }
964164426Ssam            buf.append(value);
965164426Ssam            if (escape) {
966164426Ssam                buf.append('"');
967164426Ssam            }
968164426Ssam        }
969164426Ssam    }
970164426Ssam
971164426Ssam    private void property(final String key, final String value) {
972164426Ssam        property(key, value, true);
973164426Ssam    }
974164426Ssam
975164426Ssam    private void property(final String key, final boolean value) {
976164426Ssam        property(key, Boolean.toString(value), false);
977164426Ssam    }
978164426Ssam
979164426Ssam    private void property(final String key, final int value) {
980164426Ssam        property(key, Integer.toString(value), false);
981164426Ssam    }
982164426Ssam
983164426Ssam    private void property(final String key) {
984164426Ssam        property(key, null);
985164426Ssam    }
986164426Ssam
987164426Ssam    private void type(final String value) {
988164426Ssam        property("type", value);
989164426Ssam    }
990164426Ssam
991164426Ssam    private void objectStart(final String name) {
992164426Ssam        buf.append('"');
993164426Ssam        buf.append(name);
994164426Ssam        buf.append("\":{");
995164426Ssam    }
996164426Ssam
997164426Ssam    private void objectStart() {
998164426Ssam        buf.append('{');
999164426Ssam    }
1000164426Ssam
1001164426Ssam    private void objectEnd() {
1002164426Ssam        buf.append('}');
1003164426Ssam    }
1004164426Ssam
1005164426Ssam    private void array(final String name, final List<? extends Node> nodes) {
1006166339Skevlo        // The size, idx comparison is just to avoid trailing comma..
1007164426Ssam        final int size = nodes.size();
1008164426Ssam        int idx = 0;
1009164426Ssam        arrayStart(name);
1010164426Ssam        for (final Node node : nodes) {
1011164426Ssam            if (node != null) {
1012164426Ssam                node.accept(this);
1013164426Ssam            } else {
1014164426Ssam                nullValue();
1015164426Ssam            }
1016164426Ssam            if (idx != (size - 1)) {
1017164426Ssam                comma();
1018164426Ssam            }
1019164426Ssam            idx++;
1020164426Ssam        }
1021164426Ssam        arrayEnd();
1022164426Ssam    }
1023164426Ssam
1024164426Ssam    private void arrayStart(final String name) {
1025164426Ssam        buf.append('"');
1026164426Ssam        buf.append(name);
1027164426Ssam        buf.append('"');
1028164426Ssam        buf.append(':');
1029164426Ssam        buf.append('[');
1030194321Ssam    }
1031164426Ssam
1032164426Ssam    private void arrayEnd() {
1033164426Ssam        buf.append(']');
1034164426Ssam    }
1035164426Ssam
1036164426Ssam    private void comma() {
1037164426Ssam        buf.append(',');
1038164426Ssam    }
1039164426Ssam
1040164426Ssam    private void nullValue() {
1041164426Ssam        buf.append("null");
1042164426Ssam    }
1043164426Ssam
1044164426Ssam    private void location(final Node node) {
1045164426Ssam        if (includeLocation) {
1046164426Ssam            objectStart("loc");
1047164426Ssam
1048164426Ssam            // source name
1049194321Ssam            final Source src = lc.getCurrentFunction().getSource();
1050194321Ssam            property("source", src.getName());
1051164426Ssam            comma();
1052164426Ssam
1053164426Ssam            // start position
1054164426Ssam            objectStart("start");
1055164426Ssam            final int start = node.getStart();
1056164426Ssam            property("line", src.getLine(start));
1057164426Ssam            comma();
1058164426Ssam            property("column", src.getColumn(start));
1059164426Ssam            objectEnd();
1060164426Ssam            comma();
1061164426Ssam
1062164426Ssam            // end position
1063164426Ssam            objectStart("end");
1064164426Ssam            final int end = node.getFinish();
1065164426Ssam            property("line", src.getLine(end));
1066164426Ssam            comma();
1067243882Sglebius            property("column", src.getColumn(end));
1068164426Ssam            objectEnd();
1069164426Ssam
1070164426Ssam            // end 'loc'
1071164426Ssam            objectEnd();
1072164426Ssam
1073164426Ssam            comma();
1074164426Ssam        }
1075164426Ssam    }
1076266406Sian
1077164426Ssam    private static String quote(final String str) {
1078164426Ssam        return JSONParser.quote(str);
1079164426Ssam    }
1080164426Ssam}
1081164426Ssam