/*
* Copyright 2013 Guidewire Software, Inc.
*/
/**
*/
package gw.internal.gosu.parser;
import gw.internal.gosu.parser.expressions.NewExpression;
import gw.internal.gosu.parser.statements.FunctionStatement;
import gw.lang.parser.IParseTree;
import gw.lang.parser.IParsedElement;
import gw.lang.parser.IParsedElementWithAtLeastOneDeclaration;
import gw.lang.parser.IScriptPartId;
import gw.lang.parser.ISourceCodeTokenizer;
import gw.lang.parser.IToken;
import gw.lang.parser.exceptions.ParseIssue;
import gw.lang.parser.expressions.IMemberAccessExpression;
import gw.lang.parser.IParseIssue;
import gw.lang.parser.IStatement;
import gw.lang.parser.statements.IFunctionStatement;
import gw.lang.reflect.IType;
import gw.util.DynamicArray;
import gw.util.GosuObjectUtil;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* Intended to specify the location of a parsed element within the source.
*/
public final class ParseTree implements IParseTree
{
private static final DynamicArray<ParseTree> EMPTY_PARSE_TREE_LIST = new DynamicArray<ParseTree>(0);
private transient ParsedElement _pe;
private DynamicArray<ParseTree> _children;
private int _iOffset;
private int _iLength;
private transient IScriptPartId _scriptPart;
ParseTree( ParsedElement pe, int iOffset, int iLength, IScriptPartId scriptPart )
{
_pe = pe;
_iOffset = iOffset;
_iLength = iLength;
_scriptPart = scriptPart;
_children = EMPTY_PARSE_TREE_LIST;
}
public IType getEnclosingType()
{
return _scriptPart == null ? null : _scriptPart.getContainingType();
}
public IScriptPartId getScriptPartId()
{
return _scriptPart;
}
/**
* @return The zero-based offset of the parsed element within the source.
*/
public int getOffset()
{
return _iOffset;
}
/**
* @return The length of the parsed element in the source.
*/
public int getLength()
{
return _iLength;
}
public void setLength( int iLength )
{
_iLength = iLength;
}
/**
* @return The one based line number of the beginning of the parsed element
*/
public int getLineNum()
{
return _pe.getLineNum();
}
/**
* @return The offset from the beginning of the line where the parsed element starts
*/
public int getColumn()
{
return _pe.getColumn();
}
/**
* @return The parsed element to which this location corresponds.
*/
public ParsedElement getParsedElement()
{
return _pe;
}
/**
* @return The most distant position this location occupies i.e., offset + length - 1.
*/
public int getExtent()
{
return getOffset() + getLength() - 1;
}
/**
* @param iPosition Any position within the source.
*
* @return True if this location contains the position.
*/
public boolean contains( int iPosition )
{
return contains( iPosition, iPosition );
}
/**
* @param l A location to check.
*
* @return True if the space occupied by this location is a superset of the
* space occupied by the specified location.
*/
public boolean contains( IParseTree l )
{
return contains( l.getOffset(), l.getExtent() );
}
private boolean contains( int start, int end )
{
return start >= getOffset() &&
end <= getExtent();
}
public boolean containsOrBorders( int iPosition, boolean strict )
{
return containsOrBorders( iPosition, iPosition, strict );
}
public boolean containsOrBorders( IParseTree l, boolean strict )
{
return containsOrBorders( l.getOffset(), l.getExtent(), strict );
}
private boolean containsOrBorders( int start, int end, boolean strict )
{
int rightBounds;
if( strict )
{
rightBounds = getOffset() + getLength();
}
else
{
ParseTree nextSibling = getNextSibling();
if( nextSibling != null )
{
rightBounds = nextSibling.getOffset() - 1;
}
else
{
rightBounds = getOffset() + getLength();
}
}
return start >= getOffset() &&
end <= rightBounds;
}
public ParseTree getDeepestLocation( boolean statementsOnly, int iStart, int iEnd, boolean strict )
{
if( !containsOrBorders( iStart, iEnd, strict ) )
{
return null;
}
ParseTree deepest = null;
if(!statementsOnly || (_pe instanceof IStatement)) {
deepest = this;
}
if( _children != null )
{
for(int i = 0; i < _children.size; i++)
{
ParseTree child = (ParseTree) _children.data[i];
ParseTree l = child.getDeepestLocation( statementsOnly, iStart, iEnd, strict );
if( Search.isDeeper( deepest, l ) )
{
deepest = l;
}
}
}
Statement embededStmt = (Statement)Search.getHiddenStatement( getParsedElement() );
if( embededStmt != null && embededStmt.getLocation() != null )
{
ParseTree l = embededStmt.getLocation().getDeepestLocation( statementsOnly, iStart, iEnd, strict );
if( Search.isDeeper( deepest, l ) )
{
deepest = l;
}
}
return deepest;
}
public boolean isAncestorOf( IParseTree l )
{
while( l != null && l.getParent() != l )
{
if( (l = l.getParent()) == this )
{
return true;
}
}
return false;
}
private ParseTree getDeepestLocation( boolean statementsOnly, int iPosition, boolean strict )
{
return getDeepestLocation( statementsOnly, iPosition, iPosition, strict );
}
/**
* @param iPosition The location to check.
* @param strict Whether to match strictly or accept white spaces to the right
*
* @return The deepest descendent location containing the specified location.
*/
public ParseTree getDeepestLocation( int iPosition, boolean strict )
{
return getDeepestLocation( iPosition, iPosition, strict );
}
/**
* @param iStart The start of the segment (inclusive)
* @param iEnd The end of the segment (inclusive)
* @param strict Whether to match strictly or accept white spaces to the right
*
* @return The deepest descendent location containing the segment.
*/
public ParseTree getDeepestLocation( int iStart, int iEnd, boolean strict )
{
return getDeepestLocation( false, iStart, iEnd, strict );
}
/**
* @param iPosition The location to check.
* @param strict Whether to match strictly or accept white spaces to the right
*
* @return The deepest descendent statement location containing the specified location.
*/
public ParseTree getDeepestStatementLocation( int iPosition, boolean strict )
{
return getDeepestLocation( true, iPosition, strict );
}
/**
* @param iLineNum The one based line number to check.
* @param clsSkip A statement sublcass to ignore. Optional.
*
* @return The first statement beginning at the specified line number, or null
* if no statements start at the line number.
*/
public ParseTree getStatementAtLine( int iLineNum, Class clsSkip )
{
if( _pe instanceof NewExpression )
{
NewExpression newExpression = (NewExpression)_pe;
IType type = newExpression.getType();
if( type instanceof IGosuClassInternal && newExpression.isAnonymousClass() )
{
return (ParseTree)((IGosuClassInternal)type).getClassStatement().getLocation().getStatementAtLine( iLineNum, clsSkip );
}
}
if( (_pe instanceof Statement) && // Note we must analyze expressions e.g., block expressions can have statements
!(_pe instanceof FunctionStatement && ((FunctionStatement)_pe).getDynamicFunctionSymbol() instanceof ProgramClassFunctionSymbol) &&
(getLineNum() == iLineNum) &&
(clsSkip == null || !clsSkip.isAssignableFrom( _pe.getClass() )) )
{
return this;
}
ParseTree deepest = null;
for(int i = 0; i < _children.size; i++)
{
ParseTree child = (ParseTree) _children.data[i];
ParseTree l = child.getStatementAtLine( iLineNum, clsSkip );
if( Search.isDeeper( deepest, l ) )
{
deepest = l;
}
}
return deepest;
}
/**
* Adds a child location to this location. Note the location must cover only
* a subset of this locations area.
*
* @param l The location to add.
*/
public void addChild( IParseTree l )
{
addChild( -1, l );
}
public void addChild( int iIndex, IParseTree l )
{
if( l == null )
{
throw new NullPointerException( "Attempted to add a Null child location." );
}
if( !contains( l ) && l.getLength() > 0 )
{
throw new IllegalArgumentException( "Attempted to add a child location whose bounds extend beyond the containing location: " +
"(" + getOffset() + ", " + getExtent() + ") does not contain (" + l.getOffset() + ", " + l.getExtent() + ")" );
}
if( _children == EMPTY_PARSE_TREE_LIST )
{
_children = new DynamicArray<ParseTree>( 2 );
}
l.setParent( this );
if( iIndex >= 0 )
{
_children.add( iIndex, (ParseTree)l );
}
else
{
_children.add( (ParseTree)l );
}
}
public void removeChild( IParseTree l )
{
if( (l != null) && (_children != null) )
{
_children.remove( l );
l.setParent( null );
}
}
/**
* @return The list of child locations covered by this location.
*/
public List<IParseTree> getChildren()
{
return (List<IParseTree>) (_children == null ? Collections.<IParseTree>emptyList() : Collections.unmodifiableList( _children ));
}
public int getChildCount()
{
return _children == null ? 0 : _children.size;
}
/**
* Sets the parent location. Note the parent location must cover a superset of the
* specified location's area.
*
* @param l The parent location.
*/
public void setParent( IParseTree l )
{
if( l != null && !l.contains( this ) && getLength() > 0 )
{
throw new IllegalArgumentException( "Attempted set the parent location, but the parent location's area is not a superset of this location's area." );
}
if( _pe != null )
{
ParsedElement parentElement = (ParsedElement)_pe.getParent();
if( parentElement != null )
{
ParseTree oldParent = parentElement.getLocation();
if( oldParent != null )
{
oldParent._children.remove( this );
}
}
_pe.setParent( l == null ? null : ((ParseTree)l)._pe );
}
}
/**
* @return This location's parent location. Note the parent covers a superset of the
* this location's area.
*/
public IParseTree getParent()
{
return _pe != null && _pe.getParent() != null ? _pe.getParent().getLocation() : null;
}
/**
* Like getParent, but won't infinitely recurse if the parent turns out to be equal to this, which can happen
* when the expression in question is a program (since the outer program has the same location as the main statement).
*/
public IParseTree getParentOtherThanThis()
{
IParseTree parent = getParent();
return (parent == this ? null : parent);
}
/**
* Is just the physical location equal?
*
* @param location Location to check
*
* @return True if the given physical location's offset and extents are equal to this one's
*/
public boolean areOffsetAndExtentEqual( IParseTree location )
{
return location != null &&
location.getOffset() == getOffset() &&
location.getExtent() == getExtent();
}
public String toString()
{
return "Offset: " + getOffset() + "\n" +
"Length: " + getLength() + "\n" +
(_pe != null ? _pe.getClass().getSimpleName() : "ParsedElement") + ": " + _pe;
}
public void initLocation( ParsedElement pe, int iOffset, int iLength )
{
_pe = _pe == null ? pe : _pe;
_iOffset = iOffset;
_iLength = iLength;
}
public void compactParseTree() {
if( _children != null )
{
for(int i = 0; i < _children.size; i++)
{
ParseTree child = (ParseTree) _children.data[i];
child.compactParseTree();
}
_children.trimToSize();
}
}
public void clearParseTreeInformation()
{
// Don't clear block expressions. Yuck!!!!!
if (getParsedElement() == null || getParsedElement().shouldClearParseInfo())
{
if( _children != null )
{
for(int i = 0; i < _children.size; i++)
{
ParseTree child = (ParseTree) _children.data[i];
child.clearParseTreeInformation();
}
}
_children = EMPTY_PARSE_TREE_LIST;
if( _pe != null )
{
_pe.setLocation( null );
}
_pe = null;
}
}
public boolean areAllChildrenAfterPosition( int caret )
{
for(int i = 0; i < _children.size; i++)
{
ParseTree child = (ParseTree) _children.data[i];
if( child.getOffset() < caret )
{
return false;
}
}
return true;
}
public List<IParseTree> getDominatingLocationList()
{
ArrayList<IParseTree> dominatingLocations = new ArrayList<IParseTree>();
if( getParent() != null )
{
List<IParseTree> parentDominators = getParent().getDominatingLocationList();
dominatingLocations.addAll( parentDominators );
dominatingLocations.add( getParent() );
dominatingLocations.addAll( getParent().getChildrenBefore( this ) );
}
return dominatingLocations;
}
public List<IParseTree> getChildrenBefore( IParseTree parseTree )
{
ArrayList<IParseTree> childrenBefore = new ArrayList<IParseTree>();
for(int i = 0; i < _children.size; i++)
{
ParseTree child = (ParseTree) _children.data[i];
if( child.getOffset() < parseTree.getOffset() )
{
childrenBefore.add( child );
}
}
return childrenBefore;
}
public boolean isSiblingOf( IParseTree deepestAtEnd )
{
return getParent() != null && GosuObjectUtil.equals( getParent(), deepestAtEnd.getParent() );
}
public ParseTree getChildAfter( int point )
{
int minDistance = Integer.MAX_VALUE;
ParseTree closestTrailingChild = null;
for(int i = 0; i < _children.size; i++)
{
ParseTree child = (ParseTree) _children.data[i];
int dist = child.getOffset() - point;
if( dist > 0 && dist < minDistance )
{
minDistance = dist;
closestTrailingChild = child;
}
}
return closestTrailingChild;
}
public ParseTree getChildBefore( int point )
{
int minDistance = Integer.MAX_VALUE;
ParseTree closestTrailingChild = null;
for(int i = 0; i < _children.size; i++)
{
ParseTree child = (ParseTree) _children.data[i];
int dist = point - child.getOffset();
if( dist > 0 && dist < minDistance )
{
minDistance = dist;
closestTrailingChild = child;
}
}
return closestTrailingChild;
}
public ParseTree getChildBefore( IParseTree child )
{
return getChildBefore( child.getOffset() );
}
public ParseTree getChildAfter( IParseTree child )
{
return getChildAfter( child.getExtent() );
}
public ParseTree getFirstChildWithParsedElementType( Class<? extends IParsedElement> aClass )
{
ParseTree returnChild = null;
for(int i = 0; i < _children.size; i++)
{
ParseTree child = (ParseTree) _children.data[i];
if( aClass.isInstance( child.getParsedElement() ) && (returnChild == null || child.getOffset() < returnChild.getOffset()) )
{
returnChild = child;
}
}
return returnChild;
}
public ParseTree getLastChildWithParsedElementType( Class<? extends IParsedElement> aClass )
{
ParseTree returnChild = null;
for(int i = 0; i < _children.size; i++)
{
ParseTree child = (ParseTree) _children.data[i];
if( aClass.isInstance( child.getParsedElement() ) && (returnChild == null || child.getOffset() > returnChild.getOffset()) )
{
returnChild = child;
}
}
return returnChild;
}
public ParseTree getLastChild()
{
return getChildBefore( getExtent() );
}
public ParseTree getNextSibling()
{
IParseTree parent = getParent();
if( parent == null || parent == this )
{
return null;
}
return (ParseTree)getParent().getChildAfter( this );
}
public ParseTree getPreviousSibling()
{
if( getParent() == null )
{
return null;
}
return (ParseTree)getParent().getChildBefore( this );
}
public ParseTree getDeepestFirstChild()
{
ParseTree parseTree = this;
while( parseTree.getFirstChildWithParsedElementType( ParsedElement.class ) != null )
{
parseTree = parseTree.getFirstChildWithParsedElementType( ParsedElement.class );
}
return parseTree;
}
public Collection<IParseTree> findDescendantsWithParsedElementType( Class type )
{
ArrayList<IParseTree> matches = new ArrayList<IParseTree>();
findDescendantsWithParsedElementType( matches, type );
return matches;
}
public void addUnder( IParseTree parent )
{
int offset = parent.getOffset();
int lineNumOffset = parent.getParsedElement().getLineNum() - 1;
int columnOffset = parent.getParsedElement().getColumn();
adjustOffset( offset, lineNumOffset, columnOffset );
setParent( parent );
_scriptPart = parent.getScriptPartId();
parent.addChild( this );
}
void adjustOffset( int offset, int lineNumOffset, int columnOffset )
{
if( getLineNum() > 1 )
{
columnOffset = 0;
}
recursivelyAdjustOffset( offset, lineNumOffset, columnOffset );
if( _pe != null )
{
for( IParseIssue parseIssue : _pe.getParseIssues() )
{
((ParseIssue)parseIssue).adjustOffset( offset, lineNumOffset, columnOffset );
}
}
}
private void recursivelyAdjustOffset( int offset, int lineNumOffset, int columnOffset )
{
if( getLineNum() > 1 )
{
columnOffset = 0;
}
_iOffset += offset;
if( _pe != null )
{
_pe.adjustLineNum( lineNumOffset );
_pe.adjustColumn( columnOffset );
}
for(int i = 0; i < _children.size; i++)
{
ParseTree child = (ParseTree) _children.data[i];
child.recursivelyAdjustOffset( offset, lineNumOffset, columnOffset );
}
if( _pe instanceof IMemberAccessExpression )
{
IMemberAccessExpression mae = (IMemberAccessExpression)_pe;
mae.setStartOffset( mae.getStartOffset() + offset );
}
if( _pe instanceof IParsedElementWithAtLeastOneDeclaration )
{
IParsedElementWithAtLeastOneDeclaration peo = (IParsedElementWithAtLeastOneDeclaration) _pe;
for( String name : peo.getDeclarations() )
{
String charSeq = (String)name;
peo.setNameOffset( peo.getNameOffset( charSeq ) + offset, charSeq );
}
}
}
private void findDescendantsWithParsedElementType( ArrayList<IParseTree> matches, Class type )
{
if( type.isAssignableFrom( _pe.getClass() ) )
{
matches.add( this );
}
for(int i = 0; i < _children.size; i++)
{
ParseTree child = (ParseTree) _children.data[i];
child.findDescendantsWithParsedElementType( matches, type );
}
}
public IFunctionStatement getEnclosingFunctionStatement()
{
for( ParseTree csr = this; csr != null; csr = (ParseTree)csr.getParentOtherThanThis() )
{
ParsedElement pe = csr.getParsedElement();
if( pe instanceof IFunctionStatement )
{
return (IFunctionStatement)pe;
}
}
return null;
}
public final IParseTree getMatchingElement(int iStart, int iLength) {
if (_iOffset == iStart && _iLength == iLength) {
return this;
}
if (_children != null) {
for (int i = 0; i < _children.size; i++) {
ParseTree child = (ParseTree) _children.data[i];
IParseTree tree = child.getMatchingElement(iStart, iLength);
if (tree != null) {
return tree;
}
}
}
return null;
}
public String getTreeOutline()
{
return getTreeOutline( "" );
}
private String getTreeOutline( String strIndent )
{
List<IToken> tokens = getParsedElement().getTokens();
StringBuilder source = new StringBuilder();
source.append( "\n" + strIndent + "- <" + getParsedElement().getClass().getSimpleName() + "> Offset: " + getOffset() + " Extent: " + getExtent() );
strIndent = strIndent + " ";
StringBuilder tokenContent = new StringBuilder();
appendTokensForOutline( null, tokens, tokenContent );
if( tokenContent.length() > 0 )
{
source.append( "\n" + strIndent + "- " );
source.append( tokenContent );
}
for( IParseTree child : getChildrenSorted( ) )
{
source.append( ((ParseTree)child).getTreeOutline( strIndent ) );
tokenContent = new StringBuilder();
appendTokensForOutline( (ParseTree)child, tokens, tokenContent );
if( tokenContent.length() > 0 )
{
source.append( "\n" + strIndent + "- " );
source.append( tokenContent );
}
}
return source.toString();
}
private void appendTokensForOutline( ParseTree child, List<IToken> tokens, StringBuilder source )
{
StringBuilder sbTokens = new StringBuilder();
addTokens( child, tokens, sbTokens );
source.append( sbTokens.toString().replace( '\n', '\u014A' ).replace( '\r', '\u019D' ).replace( ' ', '\u1D13' ) );
}
public String getTextFromTokens()
{
List<IToken> tokens = getParsedElement().getTokens();
StringBuilder source = new StringBuilder();
addTokens( null, tokens, source );
for( IParseTree child : getChildrenSorted( ) )
{
source.append( child.getTextFromTokens() );
addTokens( (ParseTree)child, tokens, source );
}
return source.toString();
}
private void addTokens( ParseTree after, List<IToken> tokens, StringBuilder source )
{
for( IToken t : tokens )
{
if( t.getAfter() == after || (after != null && after.isAncestor( t.getAfter() )) )
{
if( t.getType() == ISourceCodeTokenizer.TT_EOF )
{
// Skip EOF
continue;
}
source.append( t.getText() );
}
}
}
public List<IParseTree> getChildrenSorted( )
{
List<IParseTree> children = new ArrayList<IParseTree>( _children );
Collections.sort( children,
new Comparator<IParseTree>()
{
public int compare( IParseTree o1, IParseTree o2 )
{
return o1.getOffset() - o2.getOffset();
}
} );
return children;
}
public boolean isAncestor(IParseTree child) {
if (child == null) {
return false;
}
if (this == child) {
return true;
}
if (child == child.getParent()) {
return false;
}
return isAncestor(child.getParent());
}
}