blob: 859e921fe4c150c9acc313e614f486eb75cbe4df [file] [log] [blame]
/*
* Copyright 2003-2007 the original author or authors.
*
* 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 org.codehaus.groovy.syntax;
import org.codehaus.groovy.GroovyBugError;
import java.io.StringWriter;
import java.io.PrintWriter;
/**
* An abstract base class for nodes in the concrete syntax tree that is
* the result of parsing. Note that the CSTNode is inextricably linked
* with the Token in that every CSTNode has a Token as it's root.
*
* @see antlr.Parser
* @see Token
* @see org.codehaus.groovy.syntax.Reduction
* @see org.codehaus.groovy.syntax.Types
*
* @author <a href="mailto:bob@werken.com">bob mcwhirter</a>
* @author <a href="mailto:cpoirier@dreaming.org">Chris Poirier</a>
*
* @version $Id$
*/
public abstract class CSTNode
{
//---------------------------------------------------------------------------
// NODE IDENTIFICATION AND MEANING
/**
* Returns the meaning of this node. If the node isEmpty(), returns
* the type of Token.NULL.
*/
public int getMeaning()
{
return getRoot( true ).getMeaning();
}
/**
* Sets the meaning for this node (and it's root Token). Not
* valid if the node isEmpty(). Returns the node, for convenience.
*/
public CSTNode setMeaning( int meaning )
{
getRoot().setMeaning( meaning );
return this;
}
/**
* Returns the actual type of the node. If the node isEmpty(), returns
* the type of Token.NULL.
*/
public int getType()
{
return getRoot( true ).getType();
}
/**
* Returns true if the node can be coerced to the specified type.
*/
public boolean canMean( int type )
{
return Types.canMean( getMeaning(), type );
}
/**
* Returns true if the node's meaning matches the specified type.
*/
public boolean isA( int type )
{
return Types.ofType( getMeaning(), type );
}
/**
* Returns true if the node's meaning matches any of the specified types.
*/
public boolean isOneOf( int[] types )
{
int meaning = getMeaning();
for( int i = 0; i < types.length; i++ )
{
if( Types.ofType(meaning, types[i]) )
{
return true;
}
}
return false;
}
/**
* Returns true if the node's meaning matches all of the specified types.
*/
public boolean isAllOf( int[] types )
{
int meaning = getMeaning();
for( int i = 0; i < types.length; i++ )
{
if( !Types.ofType(meaning, types[i]) )
{
return false;
}
}
return true;
}
/**
* Returns the first matching meaning of the specified types.
* Returns Types.UNKNOWN if there are no matches.
*/
public int getMeaningAs( int[] types )
{
for( int i = 0; i < types.length; i++ )
{
if( isA(types[i]) )
{
return types[i];
}
}
return Types.UNKNOWN;
}
//---------------------------------------------------------------------------
// TYPE SUGAR
/**
* Returns true if the node matches the specified type. Effectively
* a synonym for <code>isA()</code>. Missing nodes are Token.NULL.
*/
boolean matches( int type )
{
return isA(type);
}
/**
* Returns true if the node and it's first child match the specified
* types. Missing nodes are Token.NULL.
*/
boolean matches( int type, int child1 )
{
return isA(type) && get(1, true).isA(child1);
}
/**
* Returns true if the node and it's first and second child match the
* specified types. Missing nodes are Token.NULL.
*/
boolean matches( int type, int child1, int child2 )
{
return matches( type, child1 ) && get(2, true).isA(child2);
}
/**
* Returns true if the node and it's first three children match the
* specified types. Missing nodes are Token.NULL.
*/
boolean matches( int type, int child1, int child2, int child3 )
{
return matches( type, child1, child2 ) && get(3, true).isA(child3);
}
/**
* Returns true if the node an it's first four children match the
* specified types. Missing nodes have type Types.NULL.
*/
boolean matches( int type, int child1, int child2, int child3, int child4 )
{
return matches( type, child1, child2, child3 ) && get(4, true).isA(child4);
}
//---------------------------------------------------------------------------
// MEMBER ACCESS
/**
* Returns true if the node is completely empty (no root, even).
*/
public boolean isEmpty()
{
return false;
}
/**
* Returns the number of elements in the node (including root).
*/
public abstract int size();
/**
* Returns true if the node has any non-root elements.
*/
public boolean hasChildren()
{
return children() > 0;
}
/**
* Returns the number of non-root elements in the node.
*/
public int children()
{
int size = size();
if( size > 1 )
{
return size - 1;
}
return 0;
}
/**
* Returns the specified element, or null.
*/
public abstract CSTNode get( int index );
/**
* Returns the specified element, or Token.NULL if
* safe is set and the specified element is null (or doesn't
* exist).
*/
public CSTNode get( int index, boolean safe )
{
CSTNode element = get( index );
if( element == null && safe )
{
element = Token.NULL;
}
return element;
}
/**
* Returns the root of the node. By convention, all nodes have
* a Token as the first element (or root), which indicates the type
* of the node. May return null if the node <code>isEmpty()</code>.
*/
public abstract Token getRoot();
/**
* Returns the root of the node, the Token that indicates it's
* type. Returns a Token.NULL if safe and the actual root is null.
*/
public Token getRoot( boolean safe )
{
Token root = getRoot();
if( root == null && safe )
{
root = Token.NULL;
}
return root;
}
/**
* Returns the text of the root. Uses <code>getRoot(true)</code>
* to get the root, so you will only receive null in return if the
* root token returns it.
*/
public String getRootText()
{
Token root = getRoot( true );
return root.getText();
}
/**
* Returns a description of the node.
*/
public String getDescription()
{
return Types.getDescription( getMeaning() );
}
/**
* Returns the starting line of the node. Returns -1
* if not known.
*/
public int getStartLine()
{
return getRoot(true).getStartLine();
}
/**
* Returns the starting column of the node. Returns -1
* if not known.
*/
public int getStartColumn()
{
return getRoot(true).getStartColumn();
}
/**
* Marks the node a complete expression. Not all nodes support
* this operation!
*/
public void markAsExpression()
{
throw new GroovyBugError( "markAsExpression() not supported for this CSTNode type" );
}
/**
* Returns true if the node is a complete expression.
*/
public boolean isAnExpression()
{
return isA(Types.SIMPLE_EXPRESSION);
}
//---------------------------------------------------------------------------
// OPERATIONS
/**
* Adds an element to the node. Returns the element for convenience.
* Not all nodes support this operation!
*/
public CSTNode add( CSTNode element )
{
throw new GroovyBugError( "add() not supported for this CSTNode type" );
}
/**
* Adds all children of the specified node to this one. Not all
* nodes support this operation!
*/
public void addChildrenOf( CSTNode of )
{
for( int i = 1; i < of.size(); i++ )
{
add( of.get(i) );
}
}
/**
* Sets an element node in at the specified index. Returns the element
* for convenience. Not all nodes support this operation!
*/
public CSTNode set( int index, CSTNode element )
{
throw new GroovyBugError( "set() not supported for this CSTNode type" );
}
/**
* Creates a <code>Reduction</code> from this node. Returns self if the
* node is already a <code>Reduction</code>.
*/
public abstract Reduction asReduction();
//---------------------------------------------------------------------------
// STRING CONVERSION
/**
* Formats the node as a <code>String</code> and returns it.
*/
public String toString()
{
StringWriter string = new StringWriter();
write( new PrintWriter(string) );
string.flush();
return string.toString();
}
/**
* Formats the node and writes it to the specified <code>Writer</code>.
*/
public void write( PrintWriter writer )
{
write( writer, "" );
}
/**
* Formats the node and writes it to the specified <code>Writer</code>.
* The indent is prepended to each output line, and is increased for each
* recursion.
*/
protected void write( PrintWriter writer, String indent )
{
writer.print( "(" );
if( !isEmpty() )
{
Token root = getRoot( true );
int type = root.getType();
int meaning = root.getMeaning();
//
// Display our type, text, and (optional) meaning
writer.print( Types.getDescription(type) );
if( meaning != type )
{
writer.print( " as " );
writer.print( Types.getDescription(meaning) );
}
if( getStartLine() > -1 )
{
writer.print( " at " + getStartLine() + ":" + getStartColumn() );
}
String text = root.getText();
int length = text.length();
if( length > 0 )
{
writer.print( ": " );
if( length > 40 )
{
text = text.substring( 0, 17 ) + "..." + text.substring( length - 17, length );
}
writer.print( " \"" );
writer.print( text );
writer.print( "\" " );
}
else if( children() > 0 )
{
writer.print( ": " );
}
//
// Recurse to display the children.
int count = size();
if( count > 1 )
{
writer.println( "" );
String indent1 = indent + " ";
String indent2 = indent + " ";
for( int i = 1; i < count; i++ )
{
writer.print( indent1 );
writer.print( i );
writer.print( ": " );
get( i, true ).write( writer, indent2 );
}
writer.print( indent );
}
}
if( indent.length() > 0 )
{
writer.println( ")" );
}
else
{
writer.print( ")" );
}
}
}