| /* |
| * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package jdk.nashorn.internal.ir.debug; |
| |
| import static jdk.nashorn.internal.runtime.Source.sourceFor; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import jdk.nashorn.internal.ir.AccessNode; |
| import jdk.nashorn.internal.ir.BinaryNode; |
| import jdk.nashorn.internal.ir.Block; |
| import jdk.nashorn.internal.ir.BlockStatement; |
| import jdk.nashorn.internal.ir.BreakNode; |
| import jdk.nashorn.internal.ir.CallNode; |
| import jdk.nashorn.internal.ir.CaseNode; |
| import jdk.nashorn.internal.ir.CatchNode; |
| import jdk.nashorn.internal.ir.ContinueNode; |
| import jdk.nashorn.internal.ir.EmptyNode; |
| import jdk.nashorn.internal.ir.Expression; |
| import jdk.nashorn.internal.ir.ExpressionStatement; |
| import jdk.nashorn.internal.ir.ForNode; |
| import jdk.nashorn.internal.ir.FunctionNode; |
| import jdk.nashorn.internal.ir.IdentNode; |
| import jdk.nashorn.internal.ir.IfNode; |
| import jdk.nashorn.internal.ir.IndexNode; |
| import jdk.nashorn.internal.ir.JoinPredecessorExpression; |
| import jdk.nashorn.internal.ir.LabelNode; |
| import jdk.nashorn.internal.ir.LiteralNode; |
| import jdk.nashorn.internal.ir.Node; |
| import jdk.nashorn.internal.ir.ObjectNode; |
| import jdk.nashorn.internal.ir.PropertyNode; |
| import jdk.nashorn.internal.ir.ReturnNode; |
| import jdk.nashorn.internal.ir.RuntimeNode; |
| import jdk.nashorn.internal.ir.SplitNode; |
| import jdk.nashorn.internal.ir.Statement; |
| import jdk.nashorn.internal.ir.SwitchNode; |
| import jdk.nashorn.internal.ir.TernaryNode; |
| import jdk.nashorn.internal.ir.ThrowNode; |
| import jdk.nashorn.internal.ir.TryNode; |
| import jdk.nashorn.internal.ir.UnaryNode; |
| import jdk.nashorn.internal.ir.VarNode; |
| import jdk.nashorn.internal.ir.WhileNode; |
| import jdk.nashorn.internal.ir.WithNode; |
| import jdk.nashorn.internal.ir.visitor.SimpleNodeVisitor; |
| import jdk.nashorn.internal.parser.JSONParser; |
| import jdk.nashorn.internal.parser.Lexer.RegexToken; |
| import jdk.nashorn.internal.parser.Parser; |
| import jdk.nashorn.internal.parser.TokenType; |
| import jdk.nashorn.internal.runtime.Context; |
| import jdk.nashorn.internal.runtime.ParserException; |
| import jdk.nashorn.internal.runtime.Source; |
| |
| /** |
| * This IR writer produces a JSON string that represents AST as a JSON string. |
| */ |
| public final class JSONWriter extends SimpleNodeVisitor { |
| |
| /** |
| * Returns AST as JSON compatible string. |
| * |
| * @param context context |
| * @param code code to be parsed |
| * @param name name of the code source (used for location) |
| * @param includeLoc tells whether to include location information for nodes or not |
| * @return JSON string representation of AST of the supplied code |
| */ |
| public static String parse(final Context context, final String code, final String name, final boolean includeLoc) { |
| final Parser parser = new Parser(context.getEnv(), sourceFor(name, code), new Context.ThrowErrorManager(), context.getEnv()._strict, context.getLogger(Parser.class)); |
| final JSONWriter jsonWriter = new JSONWriter(includeLoc); |
| try { |
| final FunctionNode functionNode = parser.parse(); //symbol name is ":program", default |
| functionNode.accept(jsonWriter); |
| return jsonWriter.getString(); |
| } catch (final ParserException e) { |
| e.throwAsEcmaException(); |
| return null; |
| } |
| } |
| |
| @Override |
| public boolean enterJoinPredecessorExpression(final JoinPredecessorExpression joinPredecessorExpression) { |
| final Expression expr = joinPredecessorExpression.getExpression(); |
| if(expr != null) { |
| expr.accept(this); |
| } else { |
| nullValue(); |
| } |
| return false; |
| } |
| |
| @Override |
| protected boolean enterDefault(final Node node) { |
| objectStart(); |
| location(node); |
| |
| return true; |
| } |
| |
| private boolean leave() { |
| objectEnd(); |
| return false; |
| } |
| |
| @Override |
| protected Node leaveDefault(final Node node) { |
| objectEnd(); |
| return null; |
| } |
| |
| @Override |
| public boolean enterAccessNode(final AccessNode accessNode) { |
| enterDefault(accessNode); |
| |
| type("MemberExpression"); |
| comma(); |
| |
| property("object"); |
| accessNode.getBase().accept(this); |
| comma(); |
| |
| property("property", accessNode.getProperty()); |
| comma(); |
| |
| property("computed", false); |
| |
| return leave(); |
| } |
| |
| @Override |
| public boolean enterBlock(final Block block) { |
| enterDefault(block); |
| |
| type("BlockStatement"); |
| comma(); |
| |
| array("body", block.getStatements()); |
| |
| return leave(); |
| } |
| |
| @Override |
| public boolean enterBinaryNode(final BinaryNode binaryNode) { |
| enterDefault(binaryNode); |
| |
| final String name; |
| if (binaryNode.isAssignment()) { |
| name = "AssignmentExpression"; |
| } else if (binaryNode.isLogical()) { |
| name = "LogicalExpression"; |
| } else { |
| name = "BinaryExpression"; |
| } |
| |
| type(name); |
| comma(); |
| |
| property("operator", binaryNode.tokenType().getName()); |
| comma(); |
| |
| property("left"); |
| binaryNode.lhs().accept(this); |
| comma(); |
| |
| property("right"); |
| binaryNode.rhs().accept(this); |
| |
| return leave(); |
| } |
| |
| @Override |
| public boolean enterBreakNode(final BreakNode breakNode) { |
| enterDefault(breakNode); |
| |
| type("BreakStatement"); |
| comma(); |
| |
| final String label = breakNode.getLabelName(); |
| if(label != null) { |
| property("label", label); |
| } else { |
| property("label"); |
| nullValue(); |
| } |
| |
| return leave(); |
| } |
| |
| @Override |
| public boolean enterCallNode(final CallNode callNode) { |
| enterDefault(callNode); |
| |
| type("CallExpression"); |
| comma(); |
| |
| property("callee"); |
| callNode.getFunction().accept(this); |
| comma(); |
| |
| array("arguments", callNode.getArgs()); |
| |
| return leave(); |
| } |
| |
| @Override |
| public boolean enterCaseNode(final CaseNode caseNode) { |
| enterDefault(caseNode); |
| |
| type("SwitchCase"); |
| comma(); |
| |
| final Node test = caseNode.getTest(); |
| property("test"); |
| if (test != null) { |
| test.accept(this); |
| } else { |
| nullValue(); |
| } |
| comma(); |
| |
| array("consequent", caseNode.getBody().getStatements()); |
| |
| return leave(); |
| } |
| |
| @Override |
| public boolean enterCatchNode(final CatchNode catchNode) { |
| enterDefault(catchNode); |
| |
| type("CatchClause"); |
| comma(); |
| |
| property("param"); |
| catchNode.getException().accept(this); |
| comma(); |
| |
| final Node guard = catchNode.getExceptionCondition(); |
| if (guard != null) { |
| property("guard"); |
| guard.accept(this); |
| comma(); |
| } |
| |
| property("body"); |
| catchNode.getBody().accept(this); |
| |
| return leave(); |
| } |
| |
| @Override |
| public boolean enterContinueNode(final ContinueNode continueNode) { |
| enterDefault(continueNode); |
| |
| type("ContinueStatement"); |
| comma(); |
| |
| final String label = continueNode.getLabelName(); |
| if(label != null) { |
| property("label", label); |
| } else { |
| property("label"); |
| nullValue(); |
| } |
| |
| return leave(); |
| } |
| |
| @Override |
| public boolean enterEmptyNode(final EmptyNode emptyNode) { |
| enterDefault(emptyNode); |
| |
| type("EmptyStatement"); |
| |
| return leave(); |
| } |
| |
| @Override |
| public boolean enterExpressionStatement(final ExpressionStatement expressionStatement) { |
| // handle debugger statement |
| final Node expression = expressionStatement.getExpression(); |
| if (expression instanceof RuntimeNode) { |
| expression.accept(this); |
| return false; |
| } |
| |
| enterDefault(expressionStatement); |
| |
| type("ExpressionStatement"); |
| comma(); |
| |
| property("expression"); |
| expression.accept(this); |
| |
| return leave(); |
| } |
| |
| @Override |
| public boolean enterBlockStatement(final BlockStatement blockStatement) { |
| enterDefault(blockStatement); |
| |
| type("BlockStatement"); |
| comma(); |
| |
| property("block"); |
| blockStatement.getBlock().accept(this); |
| |
| return leave(); |
| } |
| |
| @Override |
| public boolean enterForNode(final ForNode forNode) { |
| enterDefault(forNode); |
| |
| if (forNode.isForIn() || (forNode.isForEach() && forNode.getInit() != null)) { |
| type("ForInStatement"); |
| comma(); |
| |
| final Node init = forNode.getInit(); |
| assert init != null; |
| property("left"); |
| init.accept(this); |
| comma(); |
| |
| final Node modify = forNode.getModify(); |
| assert modify != null; |
| property("right"); |
| modify.accept(this); |
| comma(); |
| |
| property("body"); |
| forNode.getBody().accept(this); |
| comma(); |
| |
| property("each", forNode.isForEach()); |
| } else { |
| type("ForStatement"); |
| comma(); |
| |
| final Node init = forNode.getInit(); |
| property("init"); |
| if (init != null) { |
| init.accept(this); |
| } else { |
| nullValue(); |
| } |
| comma(); |
| |
| final Node test = forNode.getTest(); |
| property("test"); |
| if (test != null) { |
| test.accept(this); |
| } else { |
| nullValue(); |
| } |
| comma(); |
| |
| final Node update = forNode.getModify(); |
| property("update"); |
| if (update != null) { |
| update.accept(this); |
| } else { |
| nullValue(); |
| } |
| comma(); |
| |
| property("body"); |
| forNode.getBody().accept(this); |
| } |
| |
| return leave(); |
| } |
| |
| @Override |
| public boolean enterFunctionNode(final FunctionNode functionNode) { |
| final boolean program = functionNode.isProgram(); |
| if (program) { |
| return emitProgram(functionNode); |
| } |
| |
| enterDefault(functionNode); |
| final String name; |
| if (functionNode.isDeclared()) { |
| name = "FunctionDeclaration"; |
| } else { |
| name = "FunctionExpression"; |
| } |
| type(name); |
| comma(); |
| |
| property("id"); |
| final FunctionNode.Kind kind = functionNode.getKind(); |
| if (functionNode.isAnonymous() || kind == FunctionNode.Kind.GETTER || kind == FunctionNode.Kind.SETTER) { |
| nullValue(); |
| } else { |
| functionNode.getIdent().accept(this); |
| } |
| comma(); |
| |
| array("params", functionNode.getParameters()); |
| comma(); |
| |
| arrayStart("defaults"); |
| arrayEnd(); |
| comma(); |
| |
| property("rest"); |
| nullValue(); |
| comma(); |
| |
| property("body"); |
| functionNode.getBody().accept(this); |
| comma(); |
| |
| property("generator", false); |
| comma(); |
| |
| property("expression", false); |
| |
| return leave(); |
| } |
| |
| private boolean emitProgram(final FunctionNode functionNode) { |
| enterDefault(functionNode); |
| type("Program"); |
| comma(); |
| |
| // body consists of nested functions and statements |
| final List<Statement> stats = functionNode.getBody().getStatements(); |
| final int size = stats.size(); |
| int idx = 0; |
| arrayStart("body"); |
| |
| for (final Node stat : stats) { |
| stat.accept(this); |
| if (idx != (size - 1)) { |
| comma(); |
| } |
| idx++; |
| } |
| arrayEnd(); |
| |
| return leave(); |
| } |
| |
| @Override |
| public boolean enterIdentNode(final IdentNode identNode) { |
| enterDefault(identNode); |
| |
| final String name = identNode.getName(); |
| if ("this".equals(name)) { |
| type("ThisExpression"); |
| } else { |
| type("Identifier"); |
| comma(); |
| property("name", identNode.getName()); |
| } |
| |
| return leave(); |
| } |
| |
| @Override |
| public boolean enterIfNode(final IfNode ifNode) { |
| enterDefault(ifNode); |
| |
| type("IfStatement"); |
| comma(); |
| |
| property("test"); |
| ifNode.getTest().accept(this); |
| comma(); |
| |
| property("consequent"); |
| ifNode.getPass().accept(this); |
| final Node elsePart = ifNode.getFail(); |
| comma(); |
| |
| property("alternate"); |
| if (elsePart != null) { |
| elsePart.accept(this); |
| } else { |
| nullValue(); |
| } |
| |
| return leave(); |
| } |
| |
| @Override |
| public boolean enterIndexNode(final IndexNode indexNode) { |
| enterDefault(indexNode); |
| |
| type("MemberExpression"); |
| comma(); |
| |
| property("object"); |
| indexNode.getBase().accept(this); |
| comma(); |
| |
| property("property"); |
| indexNode.getIndex().accept(this); |
| comma(); |
| |
| property("computed", true); |
| |
| return leave(); |
| } |
| |
| @Override |
| public boolean enterLabelNode(final LabelNode labelNode) { |
| enterDefault(labelNode); |
| |
| type("LabeledStatement"); |
| comma(); |
| |
| property("label", labelNode.getLabelName()); |
| comma(); |
| |
| property("body"); |
| labelNode.getBody().accept(this); |
| |
| return leave(); |
| } |
| |
| @SuppressWarnings("rawtypes") |
| @Override |
| public boolean enterLiteralNode(final LiteralNode literalNode) { |
| enterDefault(literalNode); |
| |
| if (literalNode instanceof LiteralNode.ArrayLiteralNode) { |
| type("ArrayExpression"); |
| comma(); |
| |
| array("elements", ((LiteralNode.ArrayLiteralNode)literalNode).getElementExpressions()); |
| } else { |
| type("Literal"); |
| comma(); |
| |
| property("value"); |
| final Object value = literalNode.getValue(); |
| if (value instanceof RegexToken) { |
| // encode RegExp literals as Strings of the form /.../<flags> |
| final RegexToken regex = (RegexToken)value; |
| final StringBuilder regexBuf = new StringBuilder(); |
| regexBuf.append('/'); |
| regexBuf.append(regex.getExpression()); |
| regexBuf.append('/'); |
| regexBuf.append(regex.getOptions()); |
| buf.append(quote(regexBuf.toString())); |
| } else { |
| final String str = literalNode.getString(); |
| // encode every String literal with prefix '$' so that script |
| // can differentiate b/w RegExps as Strings and Strings. |
| buf.append(literalNode.isString()? quote("$" + str) : str); |
| } |
| } |
| |
| return leave(); |
| } |
| |
| @Override |
| public boolean enterObjectNode(final ObjectNode objectNode) { |
| enterDefault(objectNode); |
| |
| type("ObjectExpression"); |
| comma(); |
| |
| array("properties", objectNode.getElements()); |
| |
| return leave(); |
| } |
| |
| @Override |
| public boolean enterPropertyNode(final PropertyNode propertyNode) { |
| final Node key = propertyNode.getKey(); |
| |
| final Node value = propertyNode.getValue(); |
| if (value != null) { |
| objectStart(); |
| location(propertyNode); |
| |
| property("key"); |
| key.accept(this); |
| comma(); |
| |
| property("value"); |
| value.accept(this); |
| comma(); |
| |
| property("kind", "init"); |
| |
| objectEnd(); |
| } else { |
| // getter |
| final Node getter = propertyNode.getGetter(); |
| if (getter != null) { |
| objectStart(); |
| location(propertyNode); |
| |
| property("key"); |
| key.accept(this); |
| comma(); |
| |
| property("value"); |
| getter.accept(this); |
| comma(); |
| |
| property("kind", "get"); |
| |
| objectEnd(); |
| } |
| |
| // setter |
| final Node setter = propertyNode.getSetter(); |
| if (setter != null) { |
| if (getter != null) { |
| comma(); |
| } |
| objectStart(); |
| location(propertyNode); |
| |
| property("key"); |
| key.accept(this); |
| comma(); |
| |
| property("value"); |
| setter.accept(this); |
| comma(); |
| |
| property("kind", "set"); |
| |
| objectEnd(); |
| } |
| } |
| |
| return false; |
| } |
| |
| @Override |
| public boolean enterReturnNode(final ReturnNode returnNode) { |
| enterDefault(returnNode); |
| |
| type("ReturnStatement"); |
| comma(); |
| |
| final Node arg = returnNode.getExpression(); |
| property("argument"); |
| if (arg != null) { |
| arg.accept(this); |
| } else { |
| nullValue(); |
| } |
| |
| return leave(); |
| } |
| |
| @Override |
| public boolean enterRuntimeNode(final RuntimeNode runtimeNode) { |
| final RuntimeNode.Request req = runtimeNode.getRequest(); |
| |
| if (req == RuntimeNode.Request.DEBUGGER) { |
| enterDefault(runtimeNode); |
| type("DebuggerStatement"); |
| return leave(); |
| } |
| |
| return false; |
| } |
| |
| @Override |
| public boolean enterSplitNode(final SplitNode splitNode) { |
| return false; |
| } |
| |
| @Override |
| public boolean enterSwitchNode(final SwitchNode switchNode) { |
| enterDefault(switchNode); |
| |
| type("SwitchStatement"); |
| comma(); |
| |
| property("discriminant"); |
| switchNode.getExpression().accept(this); |
| comma(); |
| |
| array("cases", switchNode.getCases()); |
| |
| return leave(); |
| } |
| |
| @Override |
| public boolean enterTernaryNode(final TernaryNode ternaryNode) { |
| enterDefault(ternaryNode); |
| |
| type("ConditionalExpression"); |
| comma(); |
| |
| property("test"); |
| ternaryNode.getTest().accept(this); |
| comma(); |
| |
| property("consequent"); |
| ternaryNode.getTrueExpression().accept(this); |
| comma(); |
| |
| property("alternate"); |
| ternaryNode.getFalseExpression().accept(this); |
| |
| return leave(); |
| } |
| |
| @Override |
| public boolean enterThrowNode(final ThrowNode throwNode) { |
| enterDefault(throwNode); |
| |
| type("ThrowStatement"); |
| comma(); |
| |
| property("argument"); |
| throwNode.getExpression().accept(this); |
| |
| return leave(); |
| } |
| |
| @Override |
| public boolean enterTryNode(final TryNode tryNode) { |
| enterDefault(tryNode); |
| |
| type("TryStatement"); |
| comma(); |
| |
| property("block"); |
| tryNode.getBody().accept(this); |
| comma(); |
| |
| |
| final List<? extends Node> catches = tryNode.getCatches(); |
| final List<CatchNode> guarded = new ArrayList<>(); |
| CatchNode unguarded = null; |
| if (catches != null) { |
| for (final Node n : catches) { |
| final CatchNode cn = (CatchNode)n; |
| if (cn.getExceptionCondition() != null) { |
| guarded.add(cn); |
| } else { |
| assert unguarded == null: "too many unguarded?"; |
| unguarded = cn; |
| } |
| } |
| } |
| |
| array("guardedHandlers", guarded); |
| comma(); |
| |
| property("handler"); |
| if (unguarded != null) { |
| unguarded.accept(this); |
| } else { |
| nullValue(); |
| } |
| comma(); |
| |
| property("finalizer"); |
| final Node finallyNode = tryNode.getFinallyBody(); |
| if (finallyNode != null) { |
| finallyNode.accept(this); |
| } else { |
| nullValue(); |
| } |
| |
| return leave(); |
| } |
| |
| @Override |
| public boolean enterUnaryNode(final UnaryNode unaryNode) { |
| enterDefault(unaryNode); |
| |
| final TokenType tokenType = unaryNode.tokenType(); |
| if (tokenType == TokenType.NEW) { |
| type("NewExpression"); |
| comma(); |
| |
| final CallNode callNode = (CallNode)unaryNode.getExpression(); |
| property("callee"); |
| callNode.getFunction().accept(this); |
| comma(); |
| |
| array("arguments", callNode.getArgs()); |
| } else { |
| final String operator; |
| final boolean prefix; |
| switch (tokenType) { |
| case INCPOSTFIX: |
| prefix = false; |
| operator = "++"; |
| break; |
| case DECPOSTFIX: |
| prefix = false; |
| operator = "--"; |
| break; |
| case INCPREFIX: |
| operator = "++"; |
| prefix = true; |
| break; |
| case DECPREFIX: |
| operator = "--"; |
| prefix = true; |
| break; |
| default: |
| prefix = true; |
| operator = tokenType.getName(); |
| break; |
| } |
| |
| type(unaryNode.isAssignment()? "UpdateExpression" : "UnaryExpression"); |
| comma(); |
| |
| property("operator", operator); |
| comma(); |
| |
| property("prefix", prefix); |
| comma(); |
| |
| property("argument"); |
| unaryNode.getExpression().accept(this); |
| } |
| |
| return leave(); |
| } |
| |
| @Override |
| public boolean enterVarNode(final VarNode varNode) { |
| final Node init = varNode.getInit(); |
| if (init instanceof FunctionNode && ((FunctionNode)init).isDeclared()) { |
| // function declaration - don't emit VariableDeclaration instead |
| // just emit FunctionDeclaration using 'init' Node. |
| init.accept(this); |
| return false; |
| } |
| |
| enterDefault(varNode); |
| |
| type("VariableDeclaration"); |
| comma(); |
| |
| arrayStart("declarations"); |
| |
| // VariableDeclarator |
| objectStart(); |
| location(varNode.getName()); |
| |
| type("VariableDeclarator"); |
| comma(); |
| |
| property("id"); |
| varNode.getName().accept(this); |
| comma(); |
| |
| property("init"); |
| if (init != null) { |
| init.accept(this); |
| } else { |
| nullValue(); |
| } |
| |
| // VariableDeclarator |
| objectEnd(); |
| |
| // declarations |
| arrayEnd(); |
| |
| return leave(); |
| } |
| |
| @Override |
| public boolean enterWhileNode(final WhileNode whileNode) { |
| enterDefault(whileNode); |
| |
| type(whileNode.isDoWhile() ? "DoWhileStatement" : "WhileStatement"); |
| comma(); |
| |
| if (whileNode.isDoWhile()) { |
| property("body"); |
| whileNode.getBody().accept(this); |
| comma(); |
| |
| property("test"); |
| whileNode.getTest().accept(this); |
| } else { |
| property("test"); |
| whileNode.getTest().accept(this); |
| comma(); |
| |
| property("body"); |
| whileNode.getBody().accept(this); |
| } |
| |
| return leave(); |
| } |
| |
| @Override |
| public boolean enterWithNode(final WithNode withNode) { |
| enterDefault(withNode); |
| |
| type("WithStatement"); |
| comma(); |
| |
| property("object"); |
| withNode.getExpression().accept(this); |
| comma(); |
| |
| property("body"); |
| withNode.getBody().accept(this); |
| |
| return leave(); |
| } |
| |
| // Internals below |
| |
| private JSONWriter(final boolean includeLocation) { |
| this.buf = new StringBuilder(); |
| this.includeLocation = includeLocation; |
| } |
| |
| private final StringBuilder buf; |
| private final boolean includeLocation; |
| |
| private String getString() { |
| return buf.toString(); |
| } |
| |
| private void property(final String key, final String value, final boolean escape) { |
| buf.append('"'); |
| buf.append(key); |
| buf.append("\":"); |
| if (value != null) { |
| if (escape) { |
| buf.append('"'); |
| } |
| buf.append(value); |
| if (escape) { |
| buf.append('"'); |
| } |
| } |
| } |
| |
| private void property(final String key, final String value) { |
| property(key, value, true); |
| } |
| |
| private void property(final String key, final boolean value) { |
| property(key, Boolean.toString(value), false); |
| } |
| |
| private void property(final String key, final int value) { |
| property(key, Integer.toString(value), false); |
| } |
| |
| private void property(final String key) { |
| property(key, null); |
| } |
| |
| private void type(final String value) { |
| property("type", value); |
| } |
| |
| private void objectStart(final String name) { |
| buf.append('"'); |
| buf.append(name); |
| buf.append("\":{"); |
| } |
| |
| private void objectStart() { |
| buf.append('{'); |
| } |
| |
| private void objectEnd() { |
| buf.append('}'); |
| } |
| |
| private void array(final String name, final List<? extends Node> nodes) { |
| // The size, idx comparison is just to avoid trailing comma.. |
| final int size = nodes.size(); |
| int idx = 0; |
| arrayStart(name); |
| for (final Node node : nodes) { |
| if (node != null) { |
| node.accept(this); |
| } else { |
| nullValue(); |
| } |
| if (idx != (size - 1)) { |
| comma(); |
| } |
| idx++; |
| } |
| arrayEnd(); |
| } |
| |
| private void arrayStart(final String name) { |
| buf.append('"'); |
| buf.append(name); |
| buf.append('"'); |
| buf.append(':'); |
| buf.append('['); |
| } |
| |
| private void arrayEnd() { |
| buf.append(']'); |
| } |
| |
| private void comma() { |
| buf.append(','); |
| } |
| |
| private void nullValue() { |
| buf.append("null"); |
| } |
| |
| private void location(final Node node) { |
| if (includeLocation) { |
| objectStart("loc"); |
| |
| // source name |
| final Source src = lc.getCurrentFunction().getSource(); |
| property("source", src.getName()); |
| comma(); |
| |
| // start position |
| objectStart("start"); |
| final int start = node.getStart(); |
| property("line", src.getLine(start)); |
| comma(); |
| property("column", src.getColumn(start)); |
| objectEnd(); |
| comma(); |
| |
| // end position |
| objectStart("end"); |
| final int end = node.getFinish(); |
| property("line", src.getLine(end)); |
| comma(); |
| property("column", src.getColumn(end)); |
| objectEnd(); |
| |
| // end 'loc' |
| objectEnd(); |
| |
| comma(); |
| } |
| } |
| |
| private static String quote(final String str) { |
| return JSONParser.quote(str); |
| } |
| } |