All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mozilla.javascript.ast.AstNode Maven / Gradle / Ivy

The newest version!
/* -*- 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 java.io.Serializable;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.mozilla.javascript.Kit;
import org.mozilla.javascript.Node;
import org.mozilla.javascript.Token;

/**
 * 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.
 *
 * 

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. 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. * *

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. * *

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. * *

{@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. * *

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++. */ public abstract class AstNode extends Node implements Comparable { protected int position = -1; protected int length = 1; protected AstNode parent; /* * Holds comments that are on same line as of actual statement e.g. * For a for loop * 1) for(var i=0; i<10; i++) //test comment { } * 2) for(var i=0; i<10; i++) * //test comment * //test comment 2 * { } * For If Statement * 1) if (x == 2) //test if comment * a = 3 + 4; //then comment * and so on */ protected AstNode inlineComment; private static Map operatorNames = new HashMap<>(); private static final int MAX_INDENT = 42; private static final String[] INDENTATIONS = new String[MAX_INDENT + 1]; 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.EXP, "**"); 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.ASSIGN_EXP, "**="); operatorNames.put(Token.VOID, "void"); StringBuilder sb = new StringBuilder(); INDENTATIONS[0] = sb.toString(); for (int i = 1; i <= MAX_INDENT; i++) { sb.append(" "); INDENTATIONS[i] = sb.toString(); } } public static class PositionComparator implements Comparator, 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. */ @Override 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.getAbsolutePosition()); } this.parent = parent; if (parent != null) { setRelative(parent.getAbsolutePosition()); } } /** * 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. * *

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. * *

* * @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) { indent = Math.min(MAX_INDENT, Math.max(0, indent)); return INDENTATIONS[indent]; } /** 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. * *

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. * *

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. * *

* * @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: case Token.YIELD_STAR: 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 void printList(List 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}. */ @Override 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 static 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(); } @Override 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()); } else if (tt == Token.STRING) { buffer.append(" ").append(((StringLiteral) node).getValue(true)); } 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(); } public AstNode getInlineComment() { return inlineComment; } public void setInlineComment(AstNode inlineComment) { this.inlineComment = inlineComment; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy