/* * Copyright (c) 2010, 2016, 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 java.lang.reflect.Field; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Deque; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import jdk.nashorn.internal.ir.BinaryNode; import jdk.nashorn.internal.ir.Block; import jdk.nashorn.internal.ir.Expression; import jdk.nashorn.internal.ir.IdentNode; import jdk.nashorn.internal.ir.Node; import jdk.nashorn.internal.ir.Statement; import jdk.nashorn.internal.ir.Symbol; import jdk.nashorn.internal.ir.Terminal; import jdk.nashorn.internal.ir.TernaryNode; import jdk.nashorn.internal.ir.annotations.Ignore; import jdk.nashorn.internal.ir.annotations.Reference; import jdk.nashorn.internal.parser.Token; import jdk.nashorn.internal.runtime.Context; import jdk.nashorn.internal.runtime.Debug; /** * AST-as-text visualizer. Sometimes you want tree form and not source * code. This works for both lowered and unlowered IR * * see the flags --print-ast and --print-ast-lower */ public final class ASTWriter { private static final ClassValue<Field[]> accessibleFields = new ClassValue<Field[]>() { @Override protected Field[] computeValue(final Class<?> type) { final Field[] fields = type.getDeclaredFields(); for(final Field f: fields) { f.setAccessible(true); } return fields; } }; /** Root node from which to start the traversal */ private final Node root; private static final int TABWIDTH = 4; /** * Constructor * @param root root of the AST to visualize */ public ASTWriter(final Node root) { this.root = root; } /** * Use the ASTWriter by instantiating it and retrieving its String * representation * * @return the string representation of the AST */ @Override public String toString() { final StringBuilder sb = new StringBuilder(); printAST(sb, null, null, "root", root, 0); return sb.toString(); } /** * Return the visited nodes in an ordered list * @return the list of nodes in order */ public Node[] toArray() { final List<Node> preorder = new ArrayList<>(); printAST(new StringBuilder(), preorder, null, "root", root, 0); return preorder.toArray(new Node[0]); } @SuppressWarnings("unchecked") private void printAST(final StringBuilder sb, final List<Node> preorder, final Field field, final String name, final Node node, final int indent) { ASTWriter.indent(sb, indent); if (node == null) { sb.append("[Object "); sb.append(name); sb.append(" null]\n"); return; } if (preorder != null) { preorder.add(node); } final boolean isReference = field != null && field.isAnnotationPresent(Reference.class); final Class<?> clazz = node.getClass(); String type = clazz.getName(); type = type.substring(type.lastIndexOf('.') + 1, type.length()); int truncate = type.indexOf("Node"); if (truncate == -1) { truncate = type.indexOf("Statement"); } if (truncate != -1) { type = type.substring(0, truncate); } type = type.toLowerCase(); if (isReference) { type = "ref: " + type; } final Symbol symbol; if (node instanceof IdentNode) { symbol = ((IdentNode)node).getSymbol(); } else { symbol = null; } if (symbol != null) { type += ">" + symbol; } if (node instanceof Block && ((Block)node).needsScope()) { type += " <scope>"; } final List<Field> children = new LinkedList<>(); if (!isReference) { enqueueChildren(node, clazz, children); } String status = ""; if (node instanceof Terminal && ((Terminal)node).isTerminal()) { status += " Terminal"; } if (node instanceof Statement && ((Statement)node).hasGoto()) { status += " Goto "; } if (symbol != null) { status += symbol; } status = status.trim(); if (!"".equals(status)) { status = " [" + status + "]"; } if (symbol != null) { String tname = ((Expression)node).getType().toString(); if (tname.indexOf('.') != -1) { tname = tname.substring(tname.lastIndexOf('.') + 1, tname.length()); } status += " (" + tname + ")"; } status += " @" + Debug.id(node); if (children.isEmpty()) { sb.append("["). append(type). append(' '). append(name). append(" = '"). append(node). append("'"). append(status). append("] "). append('\n'); } else { sb.append("["). append(type). append(' '). append(name). append(' '). append(Token.toString(node.getToken())). append(status). append("]"). append('\n'); for (final Field child : children) { if (child.isAnnotationPresent(Ignore.class)) { continue; } Object value; try { value = child.get(node); } catch (final IllegalArgumentException | IllegalAccessException e) { Context.printStackTrace(e); return; } if (value instanceof Node) { printAST(sb, preorder, child, child.getName(), (Node)value, indent + 1); } else if (value instanceof Collection) { int pos = 0; ASTWriter.indent(sb, indent + 1); sb.append('['). append(child.getName()). append("[0.."). append(((Collection<Node>)value).size()). append("]]"). append('\n'); for (final Node member : (Collection<Node>)value) { printAST(sb, preorder, child, child.getName() + "[" + pos++ + "]", member, indent + 2); } } } } } private static void enqueueChildren(final Node node, final Class<?> nodeClass, final List<Field> children) { final Deque<Class<?>> stack = new ArrayDeque<>(); /** * Here is some ugliness that can be overcome by proper ChildNode annotations * with proper orders. Right now we basically sort all classes up to Node * with super class first, as this often is the natural order, e.g. base * before index for an IndexNode. * * Also there are special cases as this is not true for UnaryNodes(lhs) and * BinaryNodes extends UnaryNode (with lhs), and TernaryNodes. * * TODO - generalize traversal with an order built on annotations and this * will go away. */ Class<?> clazz = nodeClass; do { stack.push(clazz); clazz = clazz.getSuperclass(); } while (clazz != null); if (node instanceof TernaryNode) { // HACK juggle "third" stack.push(stack.removeLast()); } // HACK change operator order for BinaryNodes to get lhs first. final Iterator<Class<?>> iter = node instanceof BinaryNode ? stack.descendingIterator() : stack.iterator(); while (iter.hasNext()) { final Class<?> c = iter.next(); for (final Field f : accessibleFields.get(c)) { try { final Object child = f.get(node); if (child == null) { continue; } if (child instanceof Node) { children.add(f); } else if (child instanceof Collection) { if (!((Collection<?>)child).isEmpty()) { children.add(f); } } } catch (final IllegalArgumentException | IllegalAccessException e) { return; } } } } private static void indent(final StringBuilder sb, final int indent) { for (int i = 0; i < indent; i++) { for (int j = 0; j < TABWIDTH; j++) { sb.append(' '); } } } }