/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.javascript.ast; import org.mozilla.javascript.Kit; import org.mozilla.javascript.Node; import org.mozilla.javascript.Token; import java.io.Serializable; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Base class for AST node types. The goal of the AST is to represent the * physical source code, to make it useful for code-processing tools such * as IDEs or pretty-printers. The parser must not rewrite the parse tree * when producing this representation. <p> * * The {@code AstNode} hierarchy sits atop the older {@link Node} class, * which was designed for code generation. The {@code Node} class is a * flexible, weakly-typed class suitable for creating and rewriting code * trees, but using it requires you to remember the exact ordering of the * child nodes, which are kept in a linked list. The {@code AstNode} * hierarchy is a strongly-typed facade with named accessors for children * and common properties, but under the hood it's still using a linked list * of child nodes. It isn't a very good idea to use the child list directly * unless you know exactly what you're doing.</p> * * Note that {@code AstNode} records additional information, including * the node's position, length, and parent node. Also, some {@code AstNode} * subclasses record some of their child nodes in instance members, since * they are not needed for code generation. In a nutshell, only the code * generator should be mixing and matching {@code AstNode} and {@code Node} * objects.<p> * * All offset fields in all subclasses of AstNode are relative to their * parent. For things like paren, bracket and keyword positions, the * position is relative to the current node. The node start position is * relative to the parent node. <p> * * During the actual parsing, node positions are absolute; adding the node to * its parent fixes up the offsets to be relative. By the time you see the AST * (e.g. using the {@code Visitor} interface), the offsets are relative. <p> * * {@code AstNode} objects have property lists accessible via the * {@link #getProp} and {@link #putProp} methods. The property lists are * integer-keyed with arbitrary {@code Object} values. For the most part the * parser generating the AST avoids using properties, preferring fields for * elements that are always set. Property lists are intended for user-defined * annotations to the tree. The Rhino code generator acts as a client and * uses node properties extensively. You are welcome to use the property-list * API for anything your client needs.<p> * * This hierarchy does not have separate branches for expressions and * statements, as the distinction in JavaScript is not as clear-cut as in * Java or C++. <p> */ public abstract class AstNode extends Node implements Comparable<AstNode> { protected int position = -1; protected int length = 1; protected AstNode parent; private static Map<Integer,String> operatorNames = new HashMap<Integer,String>(); static { operatorNames.put(Token.IN, "in"); operatorNames.put(Token.TYPEOF, "typeof"); operatorNames.put(Token.INSTANCEOF, "instanceof"); operatorNames.put(Token.DELPROP, "delete"); operatorNames.put(Token.COMMA, ","); operatorNames.put(Token.COLON, ":"); operatorNames.put(Token.OR, "||"); operatorNames.put(Token.AND, "&&"); operatorNames.put(Token.INC, "++"); operatorNames.put(Token.DEC, "--"); operatorNames.put(Token.BITOR, "|"); operatorNames.put(Token.BITXOR, "^"); operatorNames.put(Token.BITAND, "&"); operatorNames.put(Token.EQ, "=="); operatorNames.put(Token.NE, "!="); operatorNames.put(Token.LT, "<"); operatorNames.put(Token.GT, ">"); operatorNames.put(Token.LE, "<="); operatorNames.put(Token.GE, ">="); operatorNames.put(Token.LSH, "<<"); operatorNames.put(Token.RSH, ">>"); operatorNames.put(Token.URSH, ">>>"); operatorNames.put(Token.ADD, "+"); operatorNames.put(Token.SUB, "-"); operatorNames.put(Token.MUL, "*"); operatorNames.put(Token.DIV, "/"); operatorNames.put(Token.MOD, "%"); operatorNames.put(Token.NOT, "!"); operatorNames.put(Token.BITNOT, "~"); operatorNames.put(Token.POS, "+"); operatorNames.put(Token.NEG, "-"); operatorNames.put(Token.SHEQ, "==="); operatorNames.put(Token.SHNE, "!=="); operatorNames.put(Token.ASSIGN, "="); operatorNames.put(Token.ASSIGN_BITOR, "|="); operatorNames.put(Token.ASSIGN_BITAND, "&="); operatorNames.put(Token.ASSIGN_LSH, "<<="); operatorNames.put(Token.ASSIGN_RSH, ">>="); operatorNames.put(Token.ASSIGN_URSH, ">>>="); operatorNames.put(Token.ASSIGN_ADD, "+="); operatorNames.put(Token.ASSIGN_SUB, "-="); operatorNames.put(Token.ASSIGN_MUL, "*="); operatorNames.put(Token.ASSIGN_DIV, "/="); operatorNames.put(Token.ASSIGN_MOD, "%="); operatorNames.put(Token.ASSIGN_BITXOR, "^="); operatorNames.put(Token.VOID, "void"); } public static class PositionComparator implements Comparator<AstNode>, Serializable { private static final long serialVersionUID = 1L; /** * Sorts nodes by (relative) start position. The start positions are * relative to their parent, so this comparator is only meaningful for * comparing siblings. */ public int compare(AstNode n1, AstNode n2) { return n1.position - n2.position; } } public AstNode() { super(Token.ERROR); } /** * Constructs a new AstNode * @param pos the start position */ public AstNode(int pos) { this(); position = pos; } /** * Constructs a new AstNode * @param pos the start position * @param len the number of characters spanned by the node in the source * text */ public AstNode(int pos, int len) { this(); position = pos; length = len; } /** * Returns relative position in parent */ public int getPosition() { return position; } /** * Sets relative position in parent */ public void setPosition(int position) { this.position = position; } /** * Returns the absolute document position of the node. * Computes it by adding the node's relative position * to the relative positions of all its parents. */ public int getAbsolutePosition() { int pos = position; AstNode parent = this.parent; while (parent != null) { pos += parent.getPosition(); parent = parent.getParent(); } return pos; } /** * Returns node length */ public int getLength() { return length; } /** * Sets node length */ public void setLength(int length) { this.length = length; } /** * Sets the node start and end positions. * Computes the length as ({@code end} - {@code position}). */ public void setBounds(int position, int end) { setPosition(position); setLength(end - position); } /** * Make this node's position relative to a parent. * Typically only used by the parser when constructing the node. * @param parentPosition the absolute parent position; the * current node position is assumed to be absolute and is * decremented by parentPosition. */ public void setRelative(int parentPosition) { this.position -= parentPosition; } /** * Returns the node parent, or {@code null} if it has none */ public AstNode getParent() { return parent; } /** * Sets the node parent. This method automatically adjusts the * current node's start position to be relative to the new parent. * @param parent the new parent. Can be {@code null}. */ public void setParent(AstNode parent) { if (parent == this.parent) { return; } // Convert position back to absolute. if (this.parent != null) { setRelative(-this.parent.getPosition()); } this.parent = parent; if (parent != null) { setRelative(parent.getPosition()); } } /** * Adds a child or function to the end of the block. * Sets the parent of the child to this node, and fixes up * the start position of the child to be relative to this node. * Sets the length of this node to include the new child. * @param kid the child * @throws IllegalArgumentException if kid is {@code null} */ public void addChild(AstNode kid) { assertNotNull(kid); int end = kid.getPosition() + kid.getLength(); setLength(end - this.getPosition()); addChildToBack(kid); kid.setParent(this); } /** * Returns the root of the tree containing this node. * @return the {@link AstRoot} at the root of this node's parent * chain, or {@code null} if the topmost parent is not an {@code AstRoot}. */ public AstRoot getAstRoot() { AstNode parent = this; // this node could be the AstRoot while (parent != null && !(parent instanceof AstRoot)) { parent = parent.getParent(); } return (AstRoot)parent; } /** * Emits source code for this node. Callee is responsible for calling this * function recursively on children, incrementing indent as appropriate.<p> * * Note: if the parser was in error-recovery mode, some AST nodes may have * {@code null} children that are expected to be non-{@code null} * when no errors are present. In this situation, the behavior of the * {@code toSource} method is undefined: {@code toSource} * implementations may assume that the AST node is error-free, since it is * intended to be invoked only at runtime after a successful parse.<p> * * @param depth the current recursion depth, typically beginning at 0 * when called on the root node. */ public abstract String toSource(int depth); /** * Prints the source indented to depth 0. */ public String toSource() { return this.toSource(0); } /** * Constructs an indentation string. * @param indent the number of indentation steps */ public String makeIndent(int indent) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < indent; i++) { sb.append(" "); } return sb.toString(); } /** * Returns a short, descriptive name for the node, such as * "ArrayComprehension". */ public String shortName() { String classname = getClass().getName(); int last = classname.lastIndexOf("."); return classname.substring(last + 1); } /** * Returns the string name for this operator. * @param op the token type, e.g. {@link Token#ADD} or {@link Token#TYPEOF} * @return the source operator string, such as "+" or "typeof" */ public static String operatorToString(int op) { String result = operatorNames.get(op); if (result == null) throw new IllegalArgumentException("Invalid operator: " + op); return result; } /** * Visits this node and its children in an arbitrary order. <p> * * It's up to each node subclass to decide the order for processing * its children. The subclass also decides (and should document) * which child nodes are not passed to the {@code NodeVisitor}. * For instance, nodes representing keywords like {@code each} or * {@code in} may not be passed to the visitor object. The visitor * can simply query the current node for these children if desired.<p> * * Generally speaking, the order will be deterministic; the order is * whatever order is decided by each child node. Normally child nodes * will try to visit their children in lexical order, but there may * be exceptions to this rule.<p> * * @param visitor the object to call with this node and its children */ public abstract void visit(NodeVisitor visitor); // subclasses with potential side effects should override this @Override public boolean hasSideEffects() { switch (getType()) { case Token.ASSIGN: case Token.ASSIGN_ADD: case Token.ASSIGN_BITAND: case Token.ASSIGN_BITOR: case Token.ASSIGN_BITXOR: case Token.ASSIGN_DIV: case Token.ASSIGN_LSH: case Token.ASSIGN_MOD: case Token.ASSIGN_MUL: case Token.ASSIGN_RSH: case Token.ASSIGN_SUB: case Token.ASSIGN_URSH: case Token.BLOCK: case Token.BREAK: case Token.CALL: case Token.CATCH: case Token.CATCH_SCOPE: case Token.CONST: case Token.CONTINUE: case Token.DEC: case Token.DELPROP: case Token.DEL_REF: case Token.DO: case Token.ELSE: case Token.ENTERWITH: case Token.ERROR: // Avoid cascaded error messages case Token.EXPORT: case Token.EXPR_RESULT: case Token.FINALLY: case Token.FUNCTION: case Token.FOR: case Token.GOTO: case Token.IF: case Token.IFEQ: case Token.IFNE: case Token.IMPORT: case Token.INC: case Token.JSR: case Token.LABEL: case Token.LEAVEWITH: case Token.LET: case Token.LETEXPR: case Token.LOCAL_BLOCK: case Token.LOOP: case Token.NEW: case Token.REF_CALL: case Token.RETHROW: case Token.RETURN: case Token.RETURN_RESULT: case Token.SEMI: case Token.SETELEM: case Token.SETELEM_OP: case Token.SETNAME: case Token.SETPROP: case Token.SETPROP_OP: case Token.SETVAR: case Token.SET_REF: case Token.SET_REF_OP: case Token.SWITCH: case Token.TARGET: case Token.THROW: case Token.TRY: case Token.VAR: case Token.WHILE: case Token.WITH: case Token.WITHEXPR: case Token.YIELD: return true; default: return false; } } /** * Bounces an IllegalArgumentException up if arg is {@code null}. * @param arg any method argument * @throws IllegalArgumentException if the argument is {@code null} */ protected void assertNotNull(Object arg) { if (arg == null) throw new IllegalArgumentException("arg cannot be null"); } /** * Prints a comma-separated item list into a {@link StringBuilder}. * @param items a list to print * @param sb a {@link StringBuilder} into which to print */ protected <T extends AstNode> void printList(List<T> items, StringBuilder sb) { int max = items.size(); int count = 0; for (AstNode item : items) { sb.append(item.toSource(0)); if (count++ < max-1) { sb.append(", "); } else if (item instanceof EmptyExpression) { sb.append(","); } } } /** * @see Kit#codeBug */ public static RuntimeException codeBug() throws RuntimeException { throw Kit.codeBug(); } // TODO(stevey): think of a way to have polymorphic toString // methods while keeping the ability to use Node.toString for // dumping the IR with Token.printTrees. Most likely: change // Node.toString to be Node.dumpTree and change callers to use that. // For now, need original toString, to compare output to old Rhino's. // @Override // public String toString() { // return this.getClass().getName() + ": " + // Token.typeToName(getType()); // } /** * Returns the innermost enclosing function, or {@code null} if not in a * function. Begins the search with this node's parent. * @return the {@link FunctionNode} enclosing this node, else {@code null} */ public FunctionNode getEnclosingFunction() { AstNode parent = this.getParent(); while (parent != null && !(parent instanceof FunctionNode)) { parent = parent.getParent(); } return (FunctionNode)parent; } /** * Returns the innermost enclosing {@link Scope} node, or {@code null} * if we're not nested in a scope. Begins the search with this node's parent. * Note that this is not the same as the defining scope for a {@link Name}. * * @return the {@link Scope} enclosing this node, else {@code null} */ public Scope getEnclosingScope() { AstNode parent = this.getParent(); while (parent != null && !(parent instanceof Scope)) { parent = parent.getParent(); } return (Scope)parent; } /** * Permits AST nodes to be sorted based on start position and length. * This makes it easy to sort Comment and Error nodes into a set of * other AST nodes: just put them all into a {@link java.util.SortedSet}, * for instance. * @param other another node * @return -1 if this node's start position is less than {@code other}'s * start position. If tied, -1 if this node's length is less than * {@code other}'s length. If the lengths are equal, sorts abitrarily * on hashcode unless the nodes are the same per {@link #equals}. */ public int compareTo(AstNode other) { if (this.equals(other)) return 0; int abs1 = this.getAbsolutePosition(); int abs2 = other.getAbsolutePosition(); if (abs1 < abs2) return -1; if (abs2 < abs1) return 1; int len1 = this.getLength(); int len2 = other.getLength(); if (len1 < len2) return -1; if (len2 < len1) return 1; return this.hashCode() - other.hashCode(); } /** * Returns the depth of this node. The root is depth 0, its * children are depth 1, and so on. * @return the node depth in the tree */ public int depth() { return parent == null ? 0 : 1 + parent.depth(); } protected static class DebugPrintVisitor implements NodeVisitor { private StringBuilder buffer; private static final int DEBUG_INDENT = 2; public DebugPrintVisitor(StringBuilder buf) { buffer = buf; } @Override public String toString() { return buffer.toString(); } private String makeIndent(int depth) { StringBuilder sb = new StringBuilder(DEBUG_INDENT * depth); for (int i = 0; i < (DEBUG_INDENT * depth); i++) { sb.append(" "); } return sb.toString(); } public boolean visit(AstNode node) { int tt = node.getType(); String name = Token.typeToName(tt); buffer.append(node.getAbsolutePosition()).append("\t"); buffer.append(makeIndent(node.depth())); buffer.append(name).append(" "); buffer.append(node.getPosition()).append(" "); buffer.append(node.getLength()); if (tt == Token.NAME) { buffer.append(" ").append(((Name)node).getIdentifier()); } buffer.append("\n"); return true; // process kids } } /** * Return the line number recorded for this node. * If no line number was recorded, searches the parent chain. * @return the nearest line number, or -1 if none was found */ @Override public int getLineno() { if (lineno != -1) return lineno; if (parent != null) return parent.getLineno(); return -1; } /** * Returns a debugging representation of the parse tree * starting at this node. * @return a very verbose indented printout of the tree. * The format of each line is: abs-pos name position length [identifier] */ public String debugPrint() { DebugPrintVisitor dpv = new DebugPrintVisitor(new StringBuilder(1000)); visit(dpv); return dpv.toString(); } }