blob: eef182f8833e1aff10c595119fd6a80253f7b4a0 [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.royale.compiler.internal.tree.as;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import antlr.Token;
import org.apache.royale.compiler.common.ASModifier;
import org.apache.royale.compiler.common.ISourceLocation;
import org.apache.royale.compiler.common.SourceLocation;
import org.apache.royale.compiler.config.CompilerDiagnosticsConstants;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.filespecs.IFileSpecification;
import org.apache.royale.compiler.internal.common.Counter;
import org.apache.royale.compiler.internal.definitions.DefinitionBase;
import org.apache.royale.compiler.internal.parsing.as.OffsetLookup;
import org.apache.royale.compiler.internal.scopes.ASFileScope;
import org.apache.royale.compiler.internal.scopes.ASScope;
import org.apache.royale.compiler.internal.scopes.TypeScope;
import org.apache.royale.compiler.internal.semantics.PostProcessStep;
import org.apache.royale.compiler.problems.ICompilerProblem;
import org.apache.royale.compiler.scopes.IASScope;
import org.apache.royale.compiler.tree.ASTNodeID;
import org.apache.royale.compiler.tree.as.IASNode;
import org.apache.royale.compiler.tree.as.IFileNode;
import org.apache.royale.compiler.tree.as.IImportNode;
import org.apache.royale.compiler.tree.as.IPackageNode;
import org.apache.royale.compiler.tree.as.IScopedNode;
import org.apache.royale.compiler.tree.mxml.IMXMLClassDefinitionNode;
import org.apache.royale.compiler.workspaces.IWorkspace;
/**
* Base class for ActionScript parse tree nodes
*/
public abstract class NodeBase extends SourceLocation implements IASNode
{
/**
* Used in calls to CheapArray functions. It represents the type of objects
* that appear in fSymbols.
*/
protected static final IASNode[] emptyNodeArray = new IASNode[0];
/**
* Combine the attributes from the base class with the attributes specific
* to this class
*
* @param superAttributes The attributes of the base class.
* @param myAttributes The attributes of this class.
* @return attribute value
*/
public static String[] combineAttributes(String[] superAttributes, String[] myAttributes)
{
String[] combinedAttributes = new String[superAttributes.length + myAttributes.length];
for (int i = 0; i < superAttributes.length; i++)
{
combinedAttributes[i] = superAttributes[i];
}
for (int i = 0; i < myAttributes.length; i++)
{
combinedAttributes[superAttributes.length + i] = myAttributes[i];
}
return combinedAttributes;
}
/**
* Constructor.
*/
public NodeBase()
{
super();
parent = null;
if (Counter.COUNT_NODES)
countNodes();
}
/**
* Parent node.
* <p>
* Methods (even in NodeBase itself) should use getParent() instead of
* accessing this member directly. getParent() is overridden in FileNode to
* do something special when the FileNode represents a shared file.
*/
protected IASNode parent;
@Override
public IASNode getParent()
{
return parent;
}
@Override
public int getChildCount()
{
return 0;
}
@Override
public IASNode getChild(int i)
{
return null;
}
@Override
public IFileSpecification getFileSpecification()
{
// TODO Make sure this works with include processing!!!
ASFileScope fileScope = getFileScope();
IWorkspace w = fileScope.getWorkspace();
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.WORKSPACE) == CompilerDiagnosticsConstants.WORKSPACE)
System.out.println("NodeBase waiting for lock in getFileSpecification");
IFileSpecification fs = w.getFileSpecification(fileScope.getContainingPath());
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.WORKSPACE) == CompilerDiagnosticsConstants.WORKSPACE)
System.out.println("NodeBase done with lock in getFileSpecification");
return fs;
}
@Override
public int getSpanningStart()
{
return getStart();
}
/**
* Get the node type as a string. For example, this will return
* "IdentifierNode" for an IdentifierNode.
*
* @return the node type
*/
public String getNodeKind()
{
return getClass().getSimpleName();
}
@Override
public boolean contains(int offset)
{
return getAbsoluteStart() < offset && getAbsoluteEnd() >= offset;
}
/**
* Determine whether the offset "loosely" fits within this node. With normal
* nodes, this will return true if the offset matches fLocation.fStart or
* fLocation.fEnd or anything in between. With bogus nodes (which have
* fStart == fEnd), this will return true if the offset comes after fStart
* and before any other nodes in the parse tree
*
* @param offset the offset to test
* @return true if the offset is "loosely" contained within this node
*/
public boolean looselyContains(int offset)
{
if (getAbsoluteStart() == getAbsoluteEnd())
{
// This is a bogus placeholder node (generally an identifier that's been inserted to complete an expression).
// We'll pretend that it extends all the way to the start offset of the next node. Now to find that next node...
if (getAbsoluteStart() <= offset)
{
NodeBase containingNode = (NodeBase)getParent();
// Step 1: Find a node that extends beyond the target offset
while (containingNode != null && containingNode.getAbsoluteEnd() <= getAbsoluteEnd())
containingNode = (NodeBase)containingNode.getParent();
// If no such node exists, we know that there aren't any tokens in the parse
// tree that end after this bogus token. So we can go ahead and pretend this node
// extends all the way out.
if (containingNode == null)
return true;
// Step 2: Find a node that starts after the target offset
IASNode succeedingNode = containingNode.getSucceedingNode(getAbsoluteEnd());
// If no such node exists, we know that there aren't any tokens in the parse
// tree that start after this bogus token. So we can go ahead and pretend this
// node extends all the way out.
if (succeedingNode == null)
return true;
// Otherwise, pretend this node extends all the way to the next token.
if (succeedingNode.getAbsoluteStart() >= offset)
return true;
}
}
else
{
// This is a real token. See if the test offset fits within (including the boundaries, since we're looking
// at "loose" containment.
return getAbsoluteStart() <= offset && getAbsoluteEnd() >= offset;
}
return false;
}
@Override
public IASNode getSucceedingNode(int offset)
{
// This node ends before the offset is even reached. This is hopeless.
if (getAbsoluteEnd() <= offset)
return null;
// See if one of our children starts after the offset
for (int i = 0; i < getChildCount(); i++)
{
IASNode child = getChild(i);
if (child.getAbsoluteStart() > offset)
return child;
else if (child.getAbsoluteEnd() > offset)
return child.getSucceedingNode(offset);
}
return null;
}
/**
* Determine whether this node is transparent. If a node is transparent, it
* can never be returned from getContainingNode... the offset will be
* considered part of the parent node instead.
*
* @return true if the node is transparent
*/
public boolean isTransparent()
{
return false;
}
@Override
public IASNode getContainingNode(int offset)
{
// This node doesn't even contain the offset. This is hopeless.
if (!contains(offset))
{
return null;
}
IASNode containingNode = this;
int childCount = getChildCount();
// See if the offset is contained within one of our children.
for (int i = 0; i < childCount; i++)
{
IASNode child = getChild(i);
if (child.getAbsoluteStart() <= offset)
{
if (child.contains(offset))
{
containingNode = child.getContainingNode(offset);
if (child instanceof NodeBase && ((NodeBase)child).canContinueContainmentSearch(containingNode, this, i, true))
continue;
break;
}
}
else
{
if (child instanceof NodeBase && ((NodeBase)child).canContinueContainmentSearch(containingNode, this, i, false))
continue;
// We've passed this offset without finding a child that contains it. This is
// the nearest enclosing node.
break;
}
}
// Make sure we don't return a transparent node
while (containingNode != null && containingNode instanceof NodeBase && ((NodeBase)containingNode).isTransparent())
{
containingNode = containingNode.getParent();
}
return containingNode;
}
@Override
public boolean isTerminal()
{
return false;
}
@Override
public IScopedNode getContainingScope()
{
return (IScopedNode)getAncestorOfType(IScopedNode.class);
}
// TODO Can probably eliminate this.
protected boolean canContinueContainmentSearch(IASNode containingNode, IASNode currentNode, int childOffset, boolean offsetsStillValid)
{
return false;
}
@Override
public IASNode getAncestorOfType(Class<? extends IASNode> nodeType)
{
IASNode current = getParent();
while (current != null && !(nodeType.isInstance(current)))
current = current.getParent();
if (current != null)
return current;
return null;
}
@Override
public String getPackageName()
{
// Starting with this node's parent, walk up the parent chain
// looking for a package node or an MXML class definition node.
// These two types of nodes understand what their package is.
IASNode current = getParent();
while (current != null &&
!(current instanceof IPackageNode ||
current instanceof IMXMLClassDefinitionNode))
{
current = current.getParent();
}
if (current instanceof IPackageNode)
return ((IPackageNode)current).getPackageName();
else if (current instanceof IMXMLClassDefinitionNode)
return ((IMXMLClassDefinitionNode)current).getPackageName();
return null;
}
/**
* Set the start offset of the node to fall just after the token. Used
* during parsing.
*
* @param token start this node after this token
*/
public void startAfter(Token token)
{
if (token instanceof ISourceLocation)
{
startAfter((ISourceLocation) token);
}
}
/**
* Set the start offset of the node to fall just after the token. Used
* during parsing.
*
* @param location start this node after this token
*/
public final void startAfter(ISourceLocation location)
{
final int end = location.getEnd();
if (end != UNKNOWN)
{
setSourcePath(location.getSourcePath());
setStart(end);
setLine(location.getEndLine());
setColumn(location.getEndColumn());
}
}
/**
* Set the start offset of the node to fall just before the token. Used
* during parsing.
*
* @param token start this node before this token
*/
public final void startBefore(Token token)
{
if (token instanceof ISourceLocation)
startBefore((ISourceLocation)token);
}
/**
* Set the start offset of the node to fall just before the token. Used
* during parsing.
*
* @param location start this node before this token
*/
public final void startBefore(ISourceLocation location)
{
final int start = location.getStart();
if (start != UNKNOWN)
{
setSourcePath(location.getSourcePath());
setStart(start);
setLine(location.getLine());
setColumn(location.getColumn());
}
}
/**
* Set the end offset of the node to fall just after the token. Used during
* parsing.
*
* @param token end this node after this token
*/
public final void endAfter(Token token)
{
if (token instanceof ISourceLocation)
endAfter((ISourceLocation)token);
}
/**
* Set the end offset of the node to fall just after the token. Used during
* parsing.
*
* @param location end this node after this token
*/
public final void endAfter(ISourceLocation location)
{
final int end = location.getEnd();
if (end != UNKNOWN)
{
setEnd(end);
setEndLine(location.getEndLine());
setEndColumn(location.getEndColumn());
}
}
/**
* Set the end offset of the node to fall just before the token. Used during
* parsing.
*
* @param token start this node before this token
*/
public final void endBefore(Token token)
{
if (token instanceof ISourceLocation)
endBefore((ISourceLocation)token);
}
/**
* Set the end offset of the node to fall just before the token. Used during
* parsing.
*
* @param location start this node before this token
*/
public final void endBefore(ISourceLocation location)
{
final int start = location.getStart();
if (start != UNKNOWN)
{
setEnd(start);
setEndLine(location.getLine());
setEndColumn(location.getColumn());
}
}
/**
* Set the start and end offsets of the node to coincide with the token's
* offsets. Used during parsing.
*
* @param token the token to take the offsets from
*/
public final void span(Token token)
{
if (token instanceof ISourceLocation)
span((ISourceLocation)token);
}
/**
* Set the start and end offsets of the node to coincide with the tokens'
* offsets. Used during parsing.
*
* @param firstToken the token to take the start offset and line number from
* @param lastToken the token to take the end offset from
*/
public final void span(Token firstToken, Token lastToken)
{
if (firstToken instanceof ISourceLocation && lastToken instanceof ISourceLocation)
span((ISourceLocation)firstToken, (ISourceLocation)lastToken);
}
/**
* Set the start and end offsets of the node. Used during parsing.
*
* @param start start offset for the node
* @param end end offset for the node
* @param line line number for the node
* @deprecated Use span(int,int,int,int,int,int) instead so that endLine and endColumn are included
*/
public final void span(int start, int end, int line, int column)
{
span(start, end, line, column, -1, -1);
}
/**
* Set the start and end offsets of the node. Used during parsing.
*
* @param start start offset for the node
* @param end end offset for the node
* @param line line number for the node
*/
public final void span(int start, int end, int line, int column, int endLine, int endColumn)
{
setStart(start);
setEnd(end);
setLine(line);
setColumn(column);
setEndLine(endLine);
setEndColumn(endColumn);
}
public Collection<ICompilerProblem> runPostProcess(EnumSet<PostProcessStep> set, ASScope containingScope)
{
ArrayList<ICompilerProblem> problems = new ArrayList<ICompilerProblem>(10);
normalize(set.contains(PostProcessStep.CALCULATE_OFFSETS));
if (set.contains(PostProcessStep.POPULATE_SCOPE) || set.contains(PostProcessStep.RECONNECT_DEFINITIONS))
analyze(set, containingScope, problems);
return problems;
}
protected void analyze(EnumSet<PostProcessStep> set, ASScope scope, Collection<ICompilerProblem> problems)
{
int childrenSize = getChildCount();
// Populate this scope with definitions found among the children
for (int i = 0; i < childrenSize; i++)
{
IASNode child = getChild(i);
if (child instanceof NodeBase)
{
if (child.getParent() == null)
((NodeBase)child).setParent(this);
((NodeBase)child).analyze(set, scope, problems);
}
}
}
/**
* Changes the position of two children in our tree. Neither child can be
* null and both must have the same parent
*
* @param child the child to replace target
* @param target the child to replace child
*/
protected void swapChildren(NodeBase child, NodeBase target)
{
//no op in this impl
}
/**
* Replaces the child with the given target. The child will be removed from
* the tree, and its parentage will be updated
*
* @param child the {@link NodeBase} to replace
* @param target the {@link NodeBase} to replace the replaced
*/
protected void replaceChild(NodeBase child, NodeBase target)
{
//no op
}
/**
* Normalize the tree. Move custom children into the real child list and
* fill in missing offsets so that offset lookup will work. Used during
* parsing.
*/
public void normalize(boolean fillInOffsets)
{
// The list of children doesn't change, so the child count should be constant throughout the loop
int childrenSize = getChildCount();
// Normalize the regular children
for (int i = 0; i < childrenSize; i++)
{
IASNode child = getChild(i);
if (child instanceof NodeBase)
{
((NodeBase)child).setParent(this);
((NodeBase)child).normalize(fillInOffsets);
}
}
// Add the special children (which get normalized as they're added)
if (childrenSize == 0)
setChildren(fillInOffsets);
// Fill in offsets based on the child nodes
if (fillInOffsets)
fillInOffsets();
// fill in any dangling ends
//fillEndOffsets();
// get rid of unused child space
}
/**
* Allow various subclasses to do special kludgy things like identify
* mx.core.Application.application
*/
protected void connectedToProjectScope()
{
// The list of children doesn't change, so the child count should be constant throughout the loop
int childrenSize = getChildCount();
for (int i = 0; i < childrenSize; i++)
{
IASNode child = getChild(i);
if (child instanceof NodeBase)
((NodeBase)child).connectedToProjectScope();
}
}
/**
* If this node has custom children (names, arguments, etc), shove them into
* the list of children. Used during parsing.
*/
protected void setChildren(boolean fillInOffsets)
{
//nothing in this class
}
/**
* If the start and end offsets haven't been set explicitly, fill them in
* based on the offsets of the children. Used during parsing. If the end
* offset is less than any of the kids' offsets, change fLocation.fEnd to
* encompass kids.
*/
protected void fillInOffsets()
{
int numChildren = getChildCount();
if (numChildren > 0)
{
int start = getAbsoluteStart();
int end = getAbsoluteEnd();
if (start == -1)
{
for (int i = 0; i < numChildren; i++)
{
IASNode child = getChild(i);
int childStart = child.getAbsoluteStart();
if (childStart != -1)
{
if (getSourcePath() == null)
setSourcePath(child.getSourcePath());
setStart(childStart);
setLine(child.getLine());
setColumn(child.getColumn());
break;
}
}
}
for (int i = numChildren - 1; i >= 0; i--)
{
IASNode child = getChild(i);
int childEnd = child.getAbsoluteEnd();
if (childEnd != -1)
{
if (end < childEnd)
{
setEnd(childEnd);
setEndLine(child.getEndLine());
setEndColumn(child.getEndColumn());
}
break;
}
}
}
}
/**
* Set the parent node. Used during parsing.
*
* @param parent parent node
*/
public void setParent(NodeBase parent)
{
this.parent = parent;
}
/**
* Get the nearest containing scope for this node. Used during type
* decoration.
*
* @return nearest containing scope for this node
*/
// TODO Make this more efficient using overrides on BlockNode and FileNode.
public IScopedNode getScopeNode()
{
if (this instanceof IScopedNode && ((IScopedNode)this).getScope() != null)
return (IScopedNode)this;
IASNode parent = getParent();
if (parent != null && parent instanceof NodeBase)
return ((NodeBase)parent).getScopeNode();
return null;
}
/**
* Get the path of the file in which this parse tree node resides.
* <p>
* The tree builder can set a node's origin source file using
* {@link #setSourcePath(String)}. If the source path was set, return the
* source path; Otherwise, use root {@link FileNode}'s path.
*
* @return file path that contains this node
*/
public String getContainingFilePath()
{
String path = getSourcePath();
if (path != null)
return path;
final FileNode fileNode = (FileNode)getAncestorOfType(FileNode.class);
if (fileNode != null)
return fileNode.getSourcePath();
return null;
}
/**
* For debugging only. Displays this node and its children in a form like
* this:
*
* <pre>
* FileNode "D:\tests\UIComponent.as" 0:0 0-467464 D:\tests\UIComponent.as
* PackageNode "mx.core" 12:1 436-465667 D:\tests\UIComponent.as
* FullNameNode "mx.core" 0:0 444-451 null
* IdentifierNode "mx" 12:9 444-446 D:\tests\UIComponent.as
* IdentifierNode "core" 12:12 447-451 D:\tests\UIComponent.as
* ScopedBlockNode 13:1 454-465667 D:\tests\UIComponent.as
* ImportNode "flash.accessibility.Accessibility" 14:1 457-497 D:\tests\UIComponent.as
* FullNameNode "flash.accessibility.Accessibility" 0:0 464-497 null
* FullNameNode "flash.accessibility" 0:0 464-483 null
* IdentifierNode "flash" 14:8 464-469 D:\tests\UIComponent.as
* IdentifierNode "accessibility" 14:14 470-483 D:\tests\UIComponent.as
* IdentifierNode "Accessibility" 14:28 484-497 D:\tests\UIComponent.as
* ...
* </pre>
* <p>
* Subclasses may not override this, because we want to maintain regularity
* for how all nodes display.
*/
@Override
public final String toString()
{
StringBuilder sb = new StringBuilder();
buildStringRecursive(sb, 0, false);
return sb.toString();
}
/**
* For debugging only. Called by {@code toString()}, and recursively by
* itself, to display this node on one line and each descendant node on
* subsequent indented lines.
*
* * */
public void buildStringRecursive(StringBuilder sb, int level, boolean skipSrcPath)
{
// Indent two spaces for each nesting level.
for (int i = 0; i < level; i++)
{
sb.append(" ");
}
// Build the string that represents this node.
buildOuterString(sb, skipSrcPath);
sb.append('\n');
//To test scopes in ParserSuite
if(skipSrcPath && (this instanceof IScopedNode)) {
for (int i = 0; i < level+1; i++)
sb.append(" ");
sb.append("[Scope]");
sb.append("\n");
IScopedNode scopedNode = (IScopedNode)this;
IASScope scope = scopedNode.getScope();
Collection<IDefinition> definitions = scope.getAllLocalDefinitions();
for(IDefinition def : definitions) {
for (int i = 0; i < level+2; i++)
sb.append(" ");
((DefinitionBase)def).buildString(sb, false);
sb.append('\n');
}
}
// Recurse over the child nodes.
int n = getChildCount();
for (int i = 0; i < n; i++)
{
NodeBase child = (NodeBase)getChild(i);
// The child can be null if we're toString()'ing
// in the debugger during tree building.
if (child != null)
child.buildStringRecursive(sb, level + 1, skipSrcPath);
}
}
/**
* For debugging only. Called by {@code buildStringRecursive()} to display
* information for this node only.
* <p>
* An example is
*
* <pre>
* PackageNode "mx.core" 12:1 436-465667 D:\tests\UIComponent.as
* </pre>.
* <p>
* The type of node (PackageNode) is displayed first, followed by
* node-specific information ("mx.core") followed by location information in
* the form
*
* <pre>
* line:column start-end sourcepath
* </pre>
*
*/
private void buildOuterString(StringBuilder sb, boolean skipSrcPath)
{
// First display the type of node, as represented by
// the short name of the node class.
sb.append(getNodeKind());
sb.append("(").append(getNodeID().name()).append(")");
sb.append(' ');
// Display optional node-specific information in the middle.
if (buildInnerString(sb))
sb.append(' ');
// The SourceLocation superclass handles producing a string such as
// "17:5 160-188" C:/test.as".
if(skipSrcPath)
sb.append(getOffsetsString());
else
sb.append(super.toString());
}
/**
* For debugging only. This method is called by {@code buildOuterString()}.
* It is overridden by subclasses to display optional node-specific
* information in the middle of the string, between the node type and the
* location information.
*/
protected boolean buildInnerString(StringBuilder sb)
{
return false;
}
/**
* For debugging only.
*/
public String getInnerString()
{
StringBuilder sb = new StringBuilder();
buildInnerString(sb);
return sb.toString();
}
public ASFileScope getFileScope()
{
ASScope scope = getASScope();
assert scope != null;
while (!(scope instanceof ASFileScope))
{
scope = scope.getContainingScope();
assert scope != null;
}
return (ASFileScope)scope;
}
/**
* @return {@link OffsetLookup} object for the current tree or null.
*/
protected final OffsetLookup tryGetOffsetLookup()
{
// Try FileNode.getOffsetLookup()
final IASNode fileNode = getAncestorOfType(IFileNode.class);
if (fileNode != null)
return ((IFileNode)fileNode).getOffsetLookup();
else
return null;
}
/**
* Get's the {@link IWorkspace} in which this {@link NodeBase} lives.
*
* @return The {@link IWorkspace} in which this {@link NodeBase} lives.
*/
public IWorkspace getWorkspace()
{
return getFileScope().getWorkspace();
}
/**
* Get the scope this Node uses for name resolution as an ASScope.
*
* @return the ASScope for this node, or null if there isn't one.
*/
public ASScope getASScope()
{
IScopedNode scopeNode = getContainingScope();
IASScope scope = scopeNode != null ? scopeNode.getScope() : null;
// If the ScopedNode had a null scope, keep looking up the tree until we
// find one with a non-null scope.
// TODO: Is it a bug that an IScopedNode returns null for it's scope?
// TODO: this seems like a leftover from block scoping - for example, a
// TODO: ForLoopNode is an IScopedNode, but it doesn't really have a scope
while (scope == null && scopeNode != null)
{
scopeNode = scopeNode.getContainingScope();
scope = scopeNode != null ? scopeNode.getScope() : null;
}
if (scope instanceof TypeScope)
{
TypeScope typeScope = (TypeScope)scope;
if (this instanceof BaseDefinitionNode)
{
if (((BaseDefinitionNode)this).hasModifier(ASModifier.STATIC))
scope = typeScope.getStaticScope();
else
scope = typeScope.getInstanceScope();
}
else
{
// Do we need the class or instance scope?
BaseDefinitionNode bdn = (BaseDefinitionNode)this.getAncestorOfType(BaseDefinitionNode.class);
if (bdn instanceof ClassNode)
{
// We must be loose code in a class
scope = typeScope.getStaticScope();
}
else
{
if (bdn != null && bdn.hasModifier(ASModifier.STATIC)
// Namespaces are always static
|| bdn instanceof NamespaceNode)
scope = typeScope.getStaticScope();
else
scope = typeScope.getInstanceScope();
}
}
}
ASScope asScope = scope instanceof ASScope ? (ASScope)scope : null;
return asScope;
}
/**
* Get the node's local start offset.
*/
@Override
public int getStart()
{
final OffsetLookup offsetLookup = tryGetOffsetLookup();
if (offsetLookup != null)
{
// to handle start offset in an included file
int absoluteOffset = getAbsoluteStart();
final String pathBeforeCaret = offsetLookup.getFilename(absoluteOffset - 1);
// if the offset is the first offset in the included file return without adjustment
if (pathBeforeCaret != null && pathBeforeCaret.equals(getSourcePath()))
return offsetLookup.getLocalOffset(absoluteOffset-1)+1;
return offsetLookup.getLocalOffset(absoluteOffset);
}
return super.getStart();
}
/**
* Get the node's local end offset.
*/
@Override
public int getEnd()
{
final OffsetLookup offsetLookup = tryGetOffsetLookup();
return offsetLookup != null ?
// to handle end offset in an included file
offsetLookup.getLocalOffset(super.getEnd() - 1) + 1 :
super.getEnd();
}
/**
* @return The node's absolute start offset.
*/
@Override
public int getAbsoluteStart()
{
return super.getStart();
}
/**
* @return The node's absolute end offset.
*/
@Override
public int getAbsoluteEnd()
{
return super.getEnd();
}
/**
* Collects the import nodes that are descendants of this node
* but which are not contained within a scoped node.
* <p>
* This is a helper method for {@link IScopedNode#getAllImportNodes}().
* Since that method walks up the chain of scoped nodes, we don't want
* to look inside scoped nodes that were already processed.
*
* @param importNodes The collection of import nodes being built.
*/
public void collectImportNodes(Collection<IImportNode> importNodes)
{
int childCount = getChildCount();
for (int i = 0; i < childCount; ++i)
{
IASNode child = getChild(i);
if (child instanceof IImportNode)
importNodes.add((IImportNode)child);
else if (!(child instanceof IScopedNode))
((NodeBase)child).collectImportNodes(importNodes);
}
}
/**
* Used only in asserts.
*/
public boolean verify()
{
// Verify the id.
ASTNodeID id = getNodeID();
assert id != null &&
id != ASTNodeID.InvalidNodeID &&
id != ASTNodeID.UnknownID : "Definition has bad id";
// TODO: Verify the source location eventually.
// assert getSourcePath() != null : "Node has null source path:\n" + toString();
// assert getStart() != UNKNOWN : "Node has unknown start:\n" + toString();
// assert getEnd() != UNKNOWN : "Node has unknown end:\n" + toString();
// assert getLine() != UNKNOWN : "Node has unknown line:\n" + toString();
// assert getColumn() != UNKNOWN : "Node has unknown column:\n" + toString();
// Verify the children.
int n = getChildCount();
for (int i = 0; i < n; i++)
{
// Any null children?
NodeBase child = (NodeBase)getChild(i);
assert child != null : "Node has null child";
// Does each child have this node as its parent?
// (Note: Two node classes override getParent() to not return the parent,
// so exclude these for now.)
if (!(child instanceof NamespaceIdentifierNode || child instanceof QualifiedNamespaceExpressionNode))
{
assert child.getParent() == this : "Child node has bad parent";
}
// Recurse on each child.
child.verify();
}
return true;
}
/**
* Counts various types of nodes that are created,
* as well as the total number of nodes.
*/
private void countNodes()
{
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.COUNTER) == CompilerDiagnosticsConstants.COUNTER)
System.out.println("ASScopeBase incrementing counter for " + getClass().getSimpleName());
Counter counter = Counter.getInstance();
counter.incrementCount(getClass().getSimpleName());
counter.incrementCount("nodes");
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.COUNTER) == CompilerDiagnosticsConstants.COUNTER)
System.out.println("ASScopeBase done incrementing counter for " + getClass().getSimpleName());
}
}