/**
* Copyright 2012 Tobias Gierke <tobias.gierke@code-sourcery.de>
*
* Licensed 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 de.codesourcery.jasm16.ast;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Stack;
import org.apache.commons.lang.StringUtils;
import de.codesourcery.jasm16.Address;
import de.codesourcery.jasm16.WordAddress;
import de.codesourcery.jasm16.compiler.ICompilationUnit;
import de.codesourcery.jasm16.compiler.SourceLocation;
import de.codesourcery.jasm16.utils.ITextRegion;
/**
* Provides various utility methods related to ASTs and AST nodes.
*
* @author tobias.gierke@code-sourcery.de
*/
public class ASTUtils {
public static void visitInOrder(ASTNode node,ISimpleASTNodeVisitor<ASTNode> visitor)
{
final Iterator<ASTNode> iterator = createInOrderIterator( node );
while( iterator.hasNext() && visitor.visit( iterator.next() ) );
}
protected enum VisitorResult { STOP, DONT_GO_DEEPER, CONTINUE_TRAVERSAL; }
protected static final class IterationContext implements IIterationContext
{
private VisitorResult result;
public void reset() { result = null; }
private void setResult(VisitorResult result)
{
if ( this.result != null ) {
throw new IllegalStateException("Result already set?");
}
this.result = result;
}
public boolean isDontGoDeeper() { return result == VisitorResult.DONT_GO_DEEPER; }
public boolean isContinueTraveral() { return result == VisitorResult.CONTINUE_TRAVERSAL; }
public boolean isStop() { return result == VisitorResult.STOP; }
public VisitorResult getResult() { return result; }
@Override
public void stop() { setResult( VisitorResult.STOP ); }
@Override
public void dontGoDeeper() { setResult( VisitorResult.DONT_GO_DEEPER); }
@Override
public void continueTraversal() { setResult( VisitorResult.CONTINUE_TRAVERSAL ); }
}
public static void visitInOrder(ASTNode node,IASTNodeVisitor<ASTNode> visitor)
{
final IterationContext context = new IterationContext();
visitInOrder( node , context , 0 , visitor );
}
public static void visitInOrder(ASTNode node,IterationContext context , int depth , IASTNodeVisitor<ASTNode> visitor)
{
context.reset();
visitor.visit( node , context );
if ( ! context.isStop() && ! context.isDontGoDeeper() )
{
for ( ASTNode child : node.getChildren() ) {
visitInOrder( child , context , depth + 1 , visitor );
}
}
}
public static void visitPostOrder(ASTNode node,IASTNodeVisitor<ASTNode> visitor)
{
final IterationContext context = new IterationContext();
visitPostOrder( node , context , 0 , visitor );
}
public static void visitPostOrder(ASTNode node,IterationContext context , int depth , IASTNodeVisitor<ASTNode> visitor)
{
int currentDepth = depth;
for ( ASTNode child : node.getChildren() )
{
visitPostOrder( child , context , currentDepth+1, visitor );
}
if ( ! context.isStop() )
{
context.reset();
visitor.visit( node , context);
}
}
/* 3
* / \
* (child 1) 2 1 (child 0)
*/
public static Iterator<ASTNode> createDepthFirst(ASTNode node)
{
final Stack<ASTNode> stack = new Stack<ASTNode>();
if ( node != null ) {
stack.push( node );
}
return new Iterator<ASTNode> () {
@Override
public boolean hasNext()
{
return ! stack.isEmpty();
}
@Override
public ASTNode next()
{
ASTNode n = stack.peek();
for ( ASTNode child : n.getChildren() ) {
stack.push( child );
}
return stack.pop();
}
@Override
public void remove()
{
throw new UnsupportedOperationException("Not implemented");
}
};
}
/* 1
* / \
* (child 1) 5 2 (child 0)
* /\
* 4 3
*/
public static Iterator<ASTNode> createInOrderIterator(ASTNode node)
{
if (node == null) {
throw new IllegalArgumentException("node must not be NULL");
}
final Stack<ASTNode> stack = new Stack<ASTNode>();
stack.push( node );
return new Iterator<ASTNode>()
{
@Override
public boolean hasNext() {
return ! stack.isEmpty();
}
/* A (1)
* |\
*(5) E B (2)
* |\
*(4) D C (3)
*/
@Override
public ASTNode next()
{
if ( stack.isEmpty() ) {
throw new NoSuchElementException();
}
ASTNode result = stack.pop();
final List<ASTNode> children = result.getChildren();
Collections.reverse( children );
for ( ASTNode child : children ) {
stack.push( child );
}
return result;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public static <T extends ASTNode> List<T> getNodesByType(final ASTNode root,final Class<T> clazz,final boolean excludeInputNode) {
final List<T> result = new ArrayList<T>();
final ISimpleASTNodeVisitor<ASTNode> visitor = new ISimpleASTNodeVisitor<ASTNode>() {
@SuppressWarnings("unchecked")
@Override
public boolean visit(ASTNode node)
{
if ( clazz.isAssignableFrom( node.getClass() ) )
{
if ( ! excludeInputNode || node != root ) {
result.add( (T) node );
}
}
return true;
}
};
// do NOT change the order here, AST#parseInternal() checks
// .org directives for ascending values and relies
// on the fact that this method returns the nodes in-order !
visitInOrder( root , visitor );
return result;
}
public static <T extends ASTNode> void visitNodesByType(ASTNode root,final ISimpleASTNodeVisitor<T> visitor, final Class<T> clazz) {
final ISimpleASTNodeVisitor<ASTNode> visitor2 = new ISimpleASTNodeVisitor<ASTNode>() {
@SuppressWarnings("unchecked")
@Override
public boolean visit(ASTNode node)
{
if ( clazz.isAssignableFrom( node.getClass() ) ) {
return visitor.visit( (T) node );
}
return true;
}
};
visitInOrder( root , visitor2 );
}
public static <T extends ASTNode> boolean containsNodeWithType(ASTNode node, Class<T> clazz)
{
final boolean[] result = { false };
final ISimpleASTNodeVisitor<T> visitor = new ISimpleASTNodeVisitor<T>() {
@Override
public boolean visit(T node)
{
result[0] = true;
return false;
}
};
visitNodesByType( node , visitor , clazz );
return result[0];
}
public static final void printAST(ASTNode n,String source) {
printAST( n , 1 , source );
}
public static final void printAST(ASTNode n,int depth,String source)
{
final String indent = StringUtils.repeat(" " , depth*2 );
System.out.println( indent + n.toString()+" >"+n.getTextRegion().apply( source )+"<" );
for ( ASTNode child : n.getChildren() ) {
printAST(child,depth+1 , source );
}
}
public static final void printAST(ASTNode n) {
printAST( n , 1 );
}
public static final void printAST(ASTNode n,int depth)
{
final String indent = StringUtils.repeat(" " , depth*2 );
System.out.println( indent + n.toString() );
for ( ASTNode child : n.getChildren() ) {
printAST(child,depth+1);
}
}
public static String printAST(ICompilationUnit unit , AST ast) throws IOException {
if ( ast == null ) {
return "<NULL AST>";
}
final StringBuilder result = new StringBuilder();
printAST( unit , ast , 0 , result );
return result.toString();
}
private static void printAST(ICompilationUnit unit, ASTNode currentNode, int currentDepth,StringBuilder result) throws IOException
{
final String indent = StringUtils.repeat(" " , currentDepth *2);
final String contents = unit.getSource( currentNode.getTextRegion() );
final String src = ">"+contents+"<";
result.append( indent + " "+currentNode.getClass().getSimpleName()+" ("+src+")").append("\n");
for ( ASTNode child : currentNode.getChildren() ) {
printAST( unit , child , currentDepth+1,result);
}
}
public static boolean isEquals(ASTNode n1,ASTNode n2)
{
final Iterator<ASTNode> it1 = ASTUtils.createInOrderIterator( n1 );
final Iterator<ASTNode> it2 = ASTUtils.createInOrderIterator( n2 );
while ( it1.hasNext() && it2.hasNext() )
{
if ( ! it1.next().equals( it2.next() ) ) {
return false;
}
}
if ( it1.hasNext() != it2.hasNext() ) {
return false;
}
return true;
}
public static int getRegisterReferenceCount(ASTNode node)
{
return ASTUtils.getNodesByType( node , RegisterReferenceNode.class , false ).size();
}
/**
* Returns the earliest memory location from a given subtree.
*
* @param node
* @return earliest memory location or <code>null</code> if the subtree either
* did not contain any {@link ObjectCodeOutputNode}s or none of the nodes had
* an address assigned to yet.
*/
public static Address getEarliestMemoryLocation(ASTNode node) {
return findMemoryLocation( node , true );
}
public static Address getLatestMemoryLocation(ASTNode node) {
return findMemoryLocation( node , false );
}
private static Address findMemoryLocation(ASTNode node,final boolean findEarliest)
{
final Address[] result = {null};
final ISimpleASTNodeVisitor<ASTNode> visitor = new ISimpleASTNodeVisitor<ASTNode>() {
@Override
public boolean visit(ASTNode node)
{
if ( node instanceof ObjectCodeOutputNode)
{
final Address adr = ((ObjectCodeOutputNode) node).getAddress();
if ( adr != null )
{
if ( result[0] == null )
{
result[0] = adr;
if ( result[0].equals( WordAddress.ZERO ) ) {
System.out.println("Node with ZERO address: "+node);
}
}
else if ( (findEarliest && adr.isLessThan( result[0] ) ) || (!findEarliest && adr.isGreaterThan( result[0] ) ) )
{
result[0] = adr;
if ( result[0].equals( WordAddress.ZERO ) ) {
System.out.println("Node with ZERO address: "+node);
}
}
}
}
return true;
}
};
visitInOrder(node , visitor );
return result[0];
}
public static void debugPrintTextRegions(ASTNode node,final String source)
{
debugPrintTextRegions(node,source,null);
}
public static void debugPrintTextRegions(ASTNode node,final String source,final ICompilationUnit unit)
{
ASTUtils.visitInOrder( node , new ISimpleASTNodeVisitor<ASTNode>()
{
@Override
public boolean visit(ASTNode node)
{
ITextRegion region = node.getTextRegion();
if ( region != null ) {
int len = region.getEndOffset()-region.getStartingOffset()-1;
if ( len < 0 ) {
len = 0;
}
String regionString = StringUtils.repeat(" ", region.getStartingOffset() )+"^"+StringUtils.repeat(" " , len )+"^";
String bodyText=null;
try {
region.apply( source ).replace("\n", "|").replace("\r","|");
bodyText = source.replace("\n", "|").replace("\r","|");
bodyText += "\n"+regionString;
} catch(Exception e) {
bodyText = "<Invalid region>";
}
String loc = "";
if ( unit != null ) {
SourceLocation sourceLocation = unit.getSourceLocation( region );
if ( sourceLocation != null ) {
loc = "[ "+sourceLocation.toString()+" ]";
}
}
System.out.print("\n-----\nAST Node "+node.getClass().getSimpleName()+" convers range "+region+" "+loc+"\n---\n"+bodyText);
} else {
System.err.print("\n-----\nAST Node "+node.getClass().getSimpleName()+" has no text region set");
}
return true;
}
});
}
}