blob: 6188a12d79358c281bdaaf793b9522fd6f647ad9 [file] [log] [blame]
/*
$Id$
Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
Redistribution and use of this software and associated documentation
("Software"), with or without modification, are permitted provided
that the following conditions are met:
1. Redistributions of source code must retain copyright
statements and notices. Redistributions must also contain a
copy of this document.
2. Redistributions in binary form must reproduce the
above copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
3. The name "groovy" must not be used to endorse or promote
products derived from this Software without prior written
permission of The Codehaus. For written permission,
please contact info@codehaus.org.
4. Products derived from this Software may not be called "groovy"
nor may "groovy" appear in their names without prior written
permission of The Codehaus. "groovy" is a registered
trademark of The Codehaus.
5. Due credit should be given to The Codehaus -
http://groovy.codehaus.org/
THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.codehaus.groovy.syntax.parser;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import groovy.lang.BitwiseNegateEvaluatingException;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.InnerClassNode;
import org.codehaus.groovy.ast.ConstructorNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.MixinNode;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.PropertyNode;
import org.codehaus.groovy.ast.Type;
import org.codehaus.groovy.ast.expr.ArrayExpression;
import org.codehaus.groovy.ast.expr.BinaryExpression;
import org.codehaus.groovy.ast.expr.BooleanExpression;
import org.codehaus.groovy.ast.expr.CastExpression;
import org.codehaus.groovy.ast.expr.ClassExpression;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.GStringExpression;
import org.codehaus.groovy.ast.expr.ListExpression;
import org.codehaus.groovy.ast.expr.MapExpression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.NegationExpression;
import org.codehaus.groovy.ast.expr.BitwiseNegExpression;
import org.codehaus.groovy.ast.expr.NotExpression;
import org.codehaus.groovy.ast.expr.PostfixExpression;
import org.codehaus.groovy.ast.expr.PrefixExpression;
import org.codehaus.groovy.ast.expr.PropertyExpression;
import org.codehaus.groovy.ast.expr.RangeExpression;
import org.codehaus.groovy.ast.expr.RegexExpression;
import org.codehaus.groovy.ast.expr.TernaryExpression;
import org.codehaus.groovy.ast.expr.TupleExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.stmt.AssertStatement;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.BreakStatement;
import org.codehaus.groovy.ast.stmt.CaseStatement;
import org.codehaus.groovy.ast.stmt.CatchStatement;
import org.codehaus.groovy.ast.stmt.ContinueStatement;
import org.codehaus.groovy.ast.stmt.DoWhileStatement;
import org.codehaus.groovy.ast.stmt.EmptyStatement;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.stmt.ForStatement;
import org.codehaus.groovy.ast.stmt.IfStatement;
import org.codehaus.groovy.ast.stmt.ReturnStatement;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.ast.stmt.SwitchStatement;
import org.codehaus.groovy.ast.stmt.SynchronizedStatement;
import org.codehaus.groovy.ast.stmt.ThrowStatement;
import org.codehaus.groovy.ast.stmt.TryCatchStatement;
import org.codehaus.groovy.ast.stmt.WhileStatement;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.syntax.Token;
import org.codehaus.groovy.syntax.Types;
import org.codehaus.groovy.syntax.Numbers;
import org.codehaus.groovy.syntax.ASTHelper;
import org.codehaus.groovy.syntax.ParserException;
import org.codehaus.groovy.syntax.CSTNode;
import org.codehaus.groovy.GroovyBugError;
import org.objectweb.asm.Constants;
/**
* Builds an Abstract Syntax Tree from the Concrete Syntax Tree produced
* by the Parser. The resulting AST is very preliminary, and must still
* be validated and massaged before it is ready to be used.
* <code>build()</code> is the primary entry point.
*
* @author James Strachan
* @author Bob McWhirter
* @author Sam Pullara
* @author Chris Poirier
*/
public class ASTBuilder extends ASTHelper
{
public ASTBuilder( SourceUnit sourceUnit, ClassLoader classLoader )
{
super(sourceUnit, classLoader);
}
//---------------------------------------------------------------------------
// ENTRY POINT
/**
* Builds an AST ModuleNode from a Parser.module() Reduction.
*/
public ModuleNode build( CSTNode input ) throws ParserException
{
makeModule();
//
// input structure:
// 1: package
// 2: imports
// 3+: statements
setPackageName( packageDeclaration( input.get(1) ));
importStatements(input.get(2) );
for( int i = 3; i < input.size(); ++i )
{
topLevelStatement( output, input.get(i) );
}
if( output.isEmpty() )
{
output.addStatement( new BlockStatement() );
}
return output;
}
//---------------------------------------------------------------------------
// DECLARATIONS
/**
* Processes the Reduction produced by Parser.packageDeclaration().
*/
protected String packageDeclaration( CSTNode reduction )
{
if( reduction.hasChildren() )
{
return makeName( reduction.get(1) );
}
return null;
}
/**
* Processes the imports Reduction produced by Parser.module().
*/
protected void importStatements(CSTNode container)
{
for( int i = 1; i < container.size(); ++i)
{
importStatement( container.get(i) );
}
}
/**
* Processes the Reduction produced by Parser.importStatement().
*/
protected void importStatement(CSTNode reduction)
{
//
// First, get the package name (if supplied).
String importPackage = makeName( reduction.get(1), null );
//
// If the first clause is Types.STAR, it's a package import.
if( reduction.get(2).isA(Types.STAR) )
{
importPackageWithStar(importPackage);
}
//
// Otherwise, it's a series of specific imports.
else
{
for( int i = 2; i < reduction.size(); i++ )
{
CSTNode clause = reduction.get(i);
String name = identifier( clause );
String as = (clause.hasChildren() ? identifier(clause.get(1)) : name);
importClass(importPackage, name, as);
}
}
}
/**
* Processes the Reduction produced by Parser.topLevelStatement().
*/
protected void topLevelStatement( ModuleNode module, CSTNode reduction ) throws ParserException
{
int type = reduction.getMeaning();
switch( type )
{
case Types.SYNTH_CLASS:
module.addClass( classDeclaration(null, reduction) );
break;
case Types.SYNTH_INTERFACE:
module.addClass( interfaceDeclaration(null, reduction) );
break;
case Types.SYNTH_METHOD:
module.addMethod( methodDeclaration(null, reduction) );
break;
default:
module.addStatement( statement(reduction) );
break;
}
}
/**
* Processes the Reduction produced by Parser.classDeclaration().
*/
protected ClassNode classDeclaration( ClassNode context, CSTNode reduction ) throws ParserException
{
//
// Calculate the easy stuff
String name = identifier( reduction );
addNewClassName(name);
int modifiers = modifiers( reduction.get(1) );
String parent = resolveName( reduction.get(2).get(1) );
//
// Then process the interface list.
CSTNode interfaceReduction = reduction.get(3);
String[] interfaces = new String[interfaceReduction.children()];
for( int i = 1; i < interfaceReduction.size(); i++ )
{
interfaces[i-1] = resolveName( interfaceReduction.get(i) );
}
//
// Create the class.
ClassNode classNode = (
context == null
? new ClassNode( dot(getPackageName(), name), modifiers, parent, interfaces, MixinNode.EMPTY_ARRAY )
: new InnerClassNode( context, dot(getPackageName(), name), modifiers, parent, interfaces, MixinNode.EMPTY_ARRAY )
);
classNode.setCSTNode( reduction.get(0) );
typeBody( classNode, reduction.get(4), 0, 0 );
return classNode;
}
/**
* Processes a type body for classDeclaration() and others.
*/
protected void typeBody( ClassNode classNode, CSTNode body, int propertyModifiers, int methodModifiers ) throws ParserException
{
for( int i = 1; i < body.size(); i++ )
{
CSTNode statement = body.get(i);
switch( statement.getMeaning() )
{
case Types.SYNTH_PROPERTY:
addPropertyDeclaration( classNode, statement, propertyModifiers );
break;
case Types.SYNTH_METHOD:
methodDeclaration( classNode, statement, methodModifiers );
break;
case Types.SYNTH_CLASS:
classDeclaration( classNode, statement );
break;
case Types.SYNTH_INTERFACE:
interfaceDeclaration( classNode, statement );
break;
default:
throw new GroovyBugError( "unrecognized type body statement [" + statement.toString() + "]" );
}
}
}
/**
* Processes the Reduction produced by Parser.propertyDeclaration().
* Adds the property to the supplied class.
*/
protected void addPropertyDeclaration( ClassNode classNode, CSTNode reduction, int extraModifiers ) throws ParserException
{
String name = identifier( reduction );
int modifiers = modifiers( reduction.get(1) ) | extraModifiers;
String type = resolveName( reduction.get(2) );
Expression value = reduction.size() > 3 ? expression(reduction.get(3)) : null;
PropertyNode propertyNode = classNode.addProperty( name, modifiers, type, value, null, null );
propertyNode.setCSTNode( reduction.get(0) );
}
/**
* A synonym for <code>addPropertyDeclaration( classNode, reduction, 0 )</code>.
*/
protected void addPropertyDeclaration( ClassNode classNode, CSTNode reduction ) throws ParserException
{
addPropertyDeclaration( classNode, reduction, 0 );
}
/**
* Processes the Reduction produced by Parser.methodDeclaration().
* Adds the method to the supplied class.
*/
protected MethodNode methodDeclaration( ClassNode classNode, CSTNode reduction, int extraModifiers ) throws ParserException
{
String className = null;
if( classNode != null )
{
className = classNode.getNameWithoutPackage();
}
//
// Get the basic method data
String name = identifier( reduction );
int modifiers = modifiers( reduction.get(1) ) | extraModifiers;
String type = resolveName( reduction.get(2) );
Parameter[] parameters = parameterDeclarations( reduction.get(3) );
BlockStatement body = statementBody( reduction.get(5) );
//
// Process the throws clause
CSTNode clause = reduction.get(4);
String[] throwTypes = new String[clause.children()];
for( int i = 1; i < clause.size(); i++ )
{
throwTypes[i-1] = resolveName( clause.get(i) );
}
if( clause.hasChildren() ) { throw new GroovyBugError( "NOT YET IMPLEMENTED: throws clause" ); }
//
// An unnamed method is a static initializer
if( name.length() == 0 )
{
throw new GroovyBugError( "NOT YET IMPLEMENTED: static initializers" );
/*
ConstructorNode node = new ConstructorNode( modifiers | Constants.ACC_STATIC, parameters, body );
node.setCSTNode( reduction.get(0) );
classNode.addConstructor( node );
return null;
*/
}
//
// A method with the class name is a constructor
else if( className != null && name.equals(className) )
{
ConstructorNode node = new ConstructorNode( modifiers, parameters, body );
node.setCSTNode( reduction.get(0) );
classNode.addConstructor( node );
return null;
}
//
// Anything else is a plain old method
else
{
MethodNode method = new MethodNode( name, modifiers, type, parameters, body );
method.setCSTNode( reduction.get(0) );
if( classNode != null )
{
classNode.addMethod( method );
}
return method;
}
}
/**
* A synonym for <code>methodDeclaration( classNode, reduction, 0 )</code>.
*/
protected MethodNode methodDeclaration( ClassNode classNode, CSTNode reduction ) throws ParserException
{
return methodDeclaration( classNode, reduction, 0 );
}
/**
* Processes the Reduction produced by Parser.parameterDeclarationList().
*/
protected Parameter[] parameterDeclarations( CSTNode reduction ) throws ParserException
{
Parameter[] parameters = new Parameter[ reduction.children() ];
for( int i = 1; i < reduction.size(); i++ )
{
CSTNode node = reduction.get(i);
String identifier = identifier( node );
String type = resolveName( node.get(1) );
if( node.size() > 2 )
{
parameters[i-1] = new Parameter( type, identifier, expression(node.get(2)) );
}
else
{
parameters[i-1] = new Parameter( type, identifier );
}
}
return parameters;
}
/**
* Processes the Reduction produced by Parser.interfaceDeclaration().
*/
protected ClassNode interfaceDeclaration( ClassNode context, CSTNode reduction ) throws ParserException
{
throw new GroovyBugError( "NOT YET IMPLEMENTED: interfaces" );
/*
//
// Calculate the easy stuff
String name = identifier( reduction );
int modifiers = modifiers( reduction.get(1) ) | Constants.ACC_ABSTRACT | Constants.ACC_STATIC;
String parent = null;
//
// Then process the interface list.
CSTNode interfaceReduction = reduction.get(3);
String[] interfaces = new String[interfaceReduction.children()];
for( int i = 1; i < interfaceReduction.size(); i++ )
{
interfaces[i-1] = resolveName( interfaceReduction.get(i) );
}
//
// Create the interface.
ClassNode classNode = (
context == null
? new ClassNode( dot(getPackageName(), name), modifiers, parent, interfaces, MixinNode.EMPTY_ARRAY )
: new InnerClassNode( context, dot(getPackageName(), name), modifiers, parent, interfaces, MixinNode.EMPTY_ARRAY )
);
classNode.setCSTNode( reduction.get(0) );
int propertyModifiers = Constants.ACC_STATIC | Constants.ACC_FINAL | Constants.ACC_PUBLIC;
int methodModifiers = Constants.ACC_ABSTRACT;
typeBody( classNode, reduction.get(4), propertyModifiers, methodModifiers );
return classNode;
*/
}
//---------------------------------------------------------------------------
// STATEMENTS
/**
* Processes the Reduction that results from Parser.statementBody().
*/
protected BlockStatement statementBody( CSTNode reduction ) throws ParserException
{
if( reduction.isEmpty() )
{
return new BlockStatement();
}
else if( reduction.getMeaning() == Types.LEFT_CURLY_BRACE )
{
return statementBlock( reduction );
}
else
{
Statement statement = statement( reduction );
statement.setCSTNode( reduction );
BlockStatement block = null;
if( statement instanceof BlockStatement )
{
block = (BlockStatement)statement;
}
else
{
block = new BlockStatement();
block.addStatement( statement );
}
return block;
}
}
/**
* Processes any series of statements, starting at the specified offset
* and running to the end of the CSTNode.
*/
protected BlockStatement statements( CSTNode reduction, int first ) throws ParserException
{
BlockStatement block = new BlockStatement();
for( int i = first; i < reduction.size(); i++ )
{
CSTNode statementReduction = reduction.get(i);
Statement statement = statement( statementReduction );
statement.setCSTNode( statementReduction );
block.addStatement( statement );
}
return block;
}
/**
* Processes any statement block.
*/
protected BlockStatement statementBlock( CSTNode reduction ) throws ParserException
{
return statements( reduction, 1 );
}
/**
* Processes the Reduction produced by Parser.statement().
*/
protected Statement statement( CSTNode reduction ) throws ParserException
{
Statement statement = null;
//
// Convert the statement
switch( reduction.getMeaning() )
{
case Types.KEYWORD_ASSERT:
{
statement = assertStatement( reduction );
break;
}
case Types.KEYWORD_BREAK:
{
statement = breakStatement( reduction );
break;
}
case Types.KEYWORD_CONTINUE:
{
statement = continueStatement( reduction );
break;
}
case Types.KEYWORD_IF:
{
statement = ifStatement( reduction );
break;
}
case Types.KEYWORD_RETURN:
{
statement = returnStatement( reduction );
break;
}
case Types.KEYWORD_SWITCH:
{
statement = switchStatement( reduction );
break;
}
case Types.KEYWORD_SYNCHRONIZED:
{
statement = synchronizedStatement( reduction );
break;
}
case Types.KEYWORD_THROW:
{
statement = throwStatement( reduction );
break;
}
case Types.KEYWORD_TRY:
{
statement = tryStatement( reduction );
break;
}
case Types.KEYWORD_FOR:
{
statement = forStatement( reduction );
break;
}
case Types.KEYWORD_WHILE:
{
statement = whileStatement( reduction );
break;
}
case Types.KEYWORD_DO:
{
statement = doWhileStatement( reduction );
break;
}
case Types.SYNTH_BLOCK:
case Types.LEFT_CURLY_BRACE:
{
statement = statementBlock( reduction );
break;
}
case Types.SYNTH_LABEL:
{
statement = statement( reduction.get(1) );
statement.setStatementLabel( identifier(reduction) );
break;
}
case Types.SYNTH_CLOSURE:
default:
{
statement = expressionStatement( reduction );
break;
}
}
statement.setCSTNode( reduction );
return statement;
}
/**
* Processes the Reduction produced by Parser.assertStatement().
*/
protected AssertStatement assertStatement( CSTNode reduction ) throws ParserException
{
BooleanExpression expression = new BooleanExpression( expression(reduction.get(1)) );
if( reduction.children() > 1 )
{
return new AssertStatement( expression, expression(reduction.get(2)) );
}
return new AssertStatement( expression, ConstantExpression.NULL );
}
/**
* Processes the Reduction produced by Parser.breakStatement().
*/
protected BreakStatement breakStatement( CSTNode reduction ) throws ParserException
{
if( reduction.hasChildren() )
{
return new BreakStatement( reduction.get(1).getRootText() );
}
return new BreakStatement();
}
/**
* Processes the Reduction produced by Parser.continueStatement().
*/
protected ContinueStatement continueStatement( CSTNode reduction ) throws ParserException
{
if( reduction.hasChildren() )
{
return new ContinueStatement( reduction.get(1).getRootText() );
}
return new ContinueStatement();
}
/**
* Processes the Reduction produced by Parser.ifStatement().
*/
protected IfStatement ifStatement( CSTNode reduction ) throws ParserException
{
Expression condition = expression( reduction.get(1) );
BlockStatement body = statementBody( reduction.get(2) );
Statement elseBlock = EmptyStatement.INSTANCE;
if( reduction.size() > 3 )
{
CSTNode elseReduction = reduction.get(3);
if( elseReduction.getMeaning() == Types.KEYWORD_IF )
{
elseBlock = ifStatement( elseReduction );
}
else
{
elseBlock = statementBody( elseReduction.get(1) );
}
}
return new IfStatement( new BooleanExpression(condition), body, elseBlock );
}
/**
* Processes the Reduction produced by Parser.returnStatement().
*/
protected ReturnStatement returnStatement( CSTNode reduction ) throws ParserException
{
if( reduction.hasChildren() )
{
return new ReturnStatement( expression(reduction.get(1)) );
}
return ReturnStatement.RETURN_NULL_OR_VOID;
}
/**
* Processes the Reduction produced by Parser.switchStatement().
*/
protected SwitchStatement switchStatement( CSTNode reduction ) throws ParserException
{
SwitchStatement statement = new SwitchStatement( expression(reduction.get(1)) );
for( int i = 2; i < reduction.size(); i++ )
{
CSTNode child = reduction.get(i);
switch( child.getMeaning() )
{
case Types.KEYWORD_CASE:
statement.addCase( caseStatement(child) );
break;
case Types.KEYWORD_DEFAULT:
statement.setDefaultStatement( statementBlock(child) );
break;
default:
throw new GroovyBugError( "invalid something in switch [" + child + "]" );
}
}
return statement;
}
/**
* Processes the Reduction produced by Parser.switchStatement() for cases.
*/
protected CaseStatement caseStatement( CSTNode reduction ) throws ParserException
{
return new CaseStatement( expression(reduction.get(1)), statements(reduction, 2) );
}
/**
* Processes the Reduction produced by Parser.synchronizedStatement().
*/
protected SynchronizedStatement synchronizedStatement( CSTNode reduction ) throws ParserException
{
return new SynchronizedStatement( expression(reduction.get(1)), statementBody(reduction.get(2)) );
}
/**
* Processes the Reduction produced by Parser.throwStatement().
*/
protected ThrowStatement throwStatement( CSTNode reduction ) throws ParserException
{
return new ThrowStatement( expression(reduction.get(1)) );
}
/**
* Processes the Reduction produced by Parser.tryStatement().
*/
protected TryCatchStatement tryStatement( CSTNode reduction ) throws ParserException
{
BlockStatement body = statementBody( reduction.get(1) );
BlockStatement finallyBlock = statementBody( reduction.get(3) );
TryCatchStatement statement = new TryCatchStatement( body, finallyBlock );
CSTNode catches = reduction.get(2);
for( int i = 1; i < catches.size(); i++ )
{
CSTNode element = catches.get(i);
String type = resolveName( element.get(1) );
String identifier = identifier( element.get(2) );
statement.addCatch( new CatchStatement(type, identifier, statementBody(element.get(3))) );
}
return statement;
}
/**
* Processes the Reduction produced by Parser.forStatement().
*/
protected ForStatement forStatement( CSTNode reduction ) throws ParserException
{
CSTNode header = reduction.get(1);
Statement body = statementBody( reduction.get(2) );
//
// If the header has type Types.UNKNOWN, it's a standard for loop.
if( header.getMeaning() == Types.UNKNOWN )
{
Expression[] init = expressions( header.get(1) );
Expression test = expression( header.get(2) );
Expression[] incr = expressions( header.get(3) );
throw new GroovyBugError( "NOT YET IMPLEMENTED: standard for loop" );
}
//
// Otherwise, it's a for each loop.
else
{
Type type = typeExpression( header.get(1) );
String identifier = identifier( header.get(2) );
Expression source = expression( header.get(3) );
return new ForStatement( identifier, type, source, body );
}
}
/**
* Processes the Reduction produced by Parser.doWhileStatement().
*/
protected DoWhileStatement doWhileStatement( CSTNode reduction ) throws ParserException
{
Expression condition = expression( reduction.get(2) );
BlockStatement body = statementBody( reduction.get(1) );
return new DoWhileStatement( new BooleanExpression(condition), body );
}
/**
* Processes the Reduction produced by Parser.whileStatement().
*/
protected WhileStatement whileStatement( CSTNode reduction ) throws ParserException
{
Expression condition = expression( reduction.get(1) );
BlockStatement body = statementBody( reduction.get(2) );
return new WhileStatement( new BooleanExpression(condition), body );
}
//---------------------------------------------------------------------------
// EXPRESSIONS
/**
* Processes any expression that forms a complete statement.
*/
protected Statement expressionStatement( CSTNode node ) throws ParserException
{
return new ExpressionStatement( expression(node) );
}
/**
* Processes a series of expression to an Expression[].
*/
protected Expression[] expressions( CSTNode reduction ) throws ParserException
{
Expression[] expressions = new Expression[ reduction.children() ];
for( int i = 1; i < reduction.size(); i++ )
{
expressions[i-1] = expression( reduction.get(i) );
}
return expressions;
}
/**
* Processes the CSTNode produced by Parser.expression().
*/
protected Expression expression( CSTNode reduction ) throws ParserException
{
Expression expression = null;
int type = reduction.getMeaningAs( EXPRESSION_HANDLERS );
switch( type )
{
case Types.SYNTHETIC:
{
expression = syntheticExpression( reduction );
break;
}
case Types.RANGE_OPERATOR:
{
Expression from = expression( reduction.get(1) );
Expression to = expression( reduction.get(2) );
expression = new RangeExpression( from, to, reduction.getMeaning() == Types.DOT_DOT );
break;
}
case Types.LEFT_SQUARE_BRACKET:
case Types.INFIX_OPERATOR:
{
expression = infixExpression( reduction );
break;
}
case Types.REGEX_PATTERN: // same to Types.BITWISE_NEGATION
{
CSTNode body = reduction.get(1);
Token token = body.getRoot();
String text = body.getRootText();
if ( body.isA(Types.SYNTH_GSTRING) )
{
expression = new RegexExpression( expression(reduction.get(1)) );
}
else
{
expression = new BitwiseNegExpression( expression(body) );
}
break;
}
case Types.PREFIX_OPERATOR:
{
expression = prefixExpression( reduction );
break;
}
case Types.POSTFIX_OPERATOR:
{
Expression body = expression( reduction.get(1) );
expression = new PostfixExpression( body, reduction.getRoot() );
break;
}
case Types.SIMPLE_EXPRESSION:
{
expression = simpleExpression( reduction );
break;
}
case Types.KEYWORD_NEW:
{
expression = newExpression( reduction );
break;
}
default:
throw new GroovyBugError( "unhandled CST: [" + reduction.toString() + "]" );
}
if( expression == null )
{
throw new GroovyBugError( "expression produced null: [" + reduction.toString() + "]" );
}
expression.setCSTNode( reduction );
return expression;
}
public static final int[] EXPRESSION_HANDLERS = {
Types.SYNTHETIC
, Types.RANGE_OPERATOR
, Types.LEFT_SQUARE_BRACKET
, Types.INFIX_OPERATOR
, Types.REGEX_PATTERN
, Types.PREFIX_OPERATOR
, Types.POSTFIX_OPERATOR
, Types.SIMPLE_EXPRESSION
, Types.KEYWORD_NEW
};
/**
* Processes most infix operators.
*/
public Expression infixExpression( CSTNode reduction ) throws ParserException
{
Expression expression;
int type = reduction.getMeaning();
switch( type )
{
case Types.DOT:
case Types.NAVIGATE:
{
String name = reduction.get(2).getRootText();
Expression context = null;
if( name.equals("class") )
{
CSTNode node = reduction.get(1);
if( node.isA(Types.LEFT_SQUARE_BRACKET) && node.children() == 1 )
{
throw new GroovyBugError( "NOT YET IMPLEMENTED: .class for array types" );
// context = classExpression( reduction.get(1) );
}
}
if( context == null )
{
context = expression( reduction.get(1) );
}
expression = new PropertyExpression( context, name, type == Types.NAVIGATE );
break;
}
case Types.KEYWORD_INSTANCEOF:
{
Expression lhs = expression( reduction.get(1) );
Expression rhs = classExpression( reduction.get(2) );
expression = new BinaryExpression( lhs, reduction.getRoot(), rhs );
break;
}
default:
{
Expression lhs = expression( reduction.get(1) );
Expression rhs = expression( reduction.get(2) );
expression = new BinaryExpression( lhs, reduction.getRoot(), rhs );
break;
}
}
return expression;
}
/**
* Processes most prefix operators.
*/
public Expression prefixExpression( CSTNode reduction ) throws ParserException
{
Expression expression = null;
CSTNode body = reduction.get(1);
int type = reduction.getMeaning();
switch( type )
{
case Types.PREFIX_MINUS:
if( body.size() == 1 && body.isA(Types.NUMBER) )
{
expression = numericExpression( body, true );
}
else
{
expression = new NegationExpression( expression(body) );
}
break;
case Types.PREFIX_PLUS:
expression = expression(body);
break;
case Types.NOT:
expression = new NotExpression( expression(body) );
break;
case Types.BITWISE_NEGATION:
expression = new BitwiseNegExpression( expression(body) );
break;
default:
expression = new PrefixExpression( reduction.getRoot(), expression(body) );
break;
}
return expression;
}
/**
* Processes most simple expressions.
*/
public Expression simpleExpression( CSTNode reduction ) throws ParserException
{
Expression expression = null;
int type = reduction.getMeaning();
switch( type )
{
case Types.KEYWORD_NULL:
expression = ConstantExpression.NULL;
break;
case Types.KEYWORD_TRUE:
expression = ConstantExpression.TRUE;
break;
case Types.KEYWORD_FALSE:
expression = ConstantExpression.FALSE;
break;
case Types.STRING:
expression = new ConstantExpression( reduction.getRootText() );
break;
case Types.INTEGER_NUMBER:
case Types.DECIMAL_NUMBER:
expression = numericExpression( reduction, false );
break;
case Types.KEYWORD_SUPER:
case Types.KEYWORD_THIS:
expression = variableExpression( reduction );
break;
case Types.IDENTIFIER:
expression = variableOrClassExpression( reduction );
break;
}
return expression;
}
/**
* Processes numeric literals.
*/
public Expression numericExpression( CSTNode reduction, boolean negate ) throws ParserException
{
Token token = reduction.getRoot();
String text = reduction.getRootText();
String signed = negate ? "-" + text : text;
boolean isInteger = (token.getMeaning() == Types.INTEGER_NUMBER);
try
{
Number number = isInteger ? Numbers.parseInteger(signed) : Numbers.parseDecimal(signed);
return new ConstantExpression( number );
}
catch( NumberFormatException e )
{
error( "numeric literal [" + signed + "] invalid or out of range for its type", token );
}
throw new GroovyBugError( "this should never happen" );
}
/**
* Processes most synthetic expressions.
*/
public Expression syntheticExpression( CSTNode reduction ) throws ParserException
{
Expression expression = null;
int type = reduction.getMeaning();
switch( type )
{
case Types.SYNTH_TERNARY:
{
BooleanExpression condition = new BooleanExpression( expression(reduction.get(1)) );
Expression trueBranch = expression( reduction.get(2) );
Expression falseBranch = expression( reduction.get(3) );
expression = new TernaryExpression( condition, trueBranch, falseBranch );
break;
}
case Types.SYNTH_CAST:
{
String className = resolveName( reduction.get(1) );
Expression body = expression( reduction.get(2) );
expression = new CastExpression( className, body );
break;
}
case Types.SYNTH_VARIABLE_DECLARATION:
{
expression = variableDeclarationExpression( reduction );
break;
}
case Types.SYNTH_METHOD_CALL:
{
expression = methodCallExpression( reduction );
break;
}
case Types.SYNTH_CLOSURE:
{
expression = closureExpression( reduction );
break;
}
case Types.SYNTH_GSTRING:
{
expression = gstringExpression( reduction );
break;
}
case Types.SYNTH_LIST:
{
expression = listExpression( reduction );
break;
}
case Types.SYNTH_MAP:
{
expression = mapExpression( reduction );
break;
}
}
return expression;
}
/**
* Converts a (typically IDENTIFIER) CSTNode to a ClassExpression, if valid,
* or a VariableExpression otherwise.
*/
protected Expression variableOrClassExpression( CSTNode reduction ) throws ParserException
{
String className = resolveName( reduction, false );
if( className == null )
{
return variableExpression( reduction );
}
else
{
return new ClassExpression( className );
}
}
/**
* Converts a CSTNode into a ClassExpression.
*/
protected ClassExpression classExpression( CSTNode reduction ) throws ParserException
{
String name = resolveName( reduction, true );
return new ClassExpression( name );
}
/**
* Converts a (typically IDENTIFIER) CSTNode to a VariableExpression, if
* valid.
*/
protected VariableExpression variableExpression( CSTNode reduction )
{
return new VariableExpression( reduction.getRootText(), null );
}
protected VariableExpression variableExpression( CSTNode reduction, String type )
{
return new VariableExpression( reduction.getRootText(), type );
}
/**
* Converts an (possibly optional) type expression to a Type.
*/
protected Type typeExpression( CSTNode reduction )
{
String name = makeName( reduction, null );
if( name == null )
{
return Type.DYNAMIC_TYPE;
}
else
{
return new Type( resolveName(name, true) );
}
}
/**
* Processes the Reduction produced by parsing a typed variable
* declaration.
*/
protected Expression variableDeclarationExpression( CSTNode reduction ) throws ParserException
{
String type = resolveName( reduction.get(1) );
//
// TEMPORARY UNTIL GENERAL SUPPORT IN PLACE
if( reduction.size() == 3 )
{
CSTNode node = reduction.get(2);
VariableExpression name = variableExpression( node, type );
//name.setType( type );
Token symbol = Token.newSymbol( Types.EQUAL, -1, -1 );
return new BinaryExpression( name, symbol, expression(node.get(1)) );
}
throw new GroovyBugError( "NOT YET IMPLEMENTED: generalized variable declarations" );
/*
VariableDeclarationExpression expression = new VariableDeclarationExpression( type );
for( i = 2; i < reduction.size(); i++ )
{
CSTNode node = reduction.get(i);
declaration.add( node.get(0), expression(node.get(1)) );
}
return expression;
*/
}
/**
* Processes a SYNTH_METHOD_CALL Reduction produced by Parser.expression().
*/
protected MethodCallExpression methodCallExpression( CSTNode reduction ) throws ParserException
{
MethodCallExpression call = null;
//
// Figure out the name and context of the method call.
CSTNode descriptor = reduction.get(1);
Expression context = null;
boolean implicit = false;
String method = "call";
boolean safe = false;
int type = descriptor.getMeaning();
switch( type )
{
case Types.KEYWORD_SUPER:
{
context = variableExpression( descriptor );
method = identifier( descriptor );
break;
}
case Types.KEYWORD_THIS:
{
context = VariableExpression.THIS_EXPRESSION;
method = identifier( descriptor );
break;
}
case Types.IDENTIFIER:
{
context = VariableExpression.THIS_EXPRESSION;
method = identifier( descriptor );
implicit = true;
break;
}
case Types.DOT:
case Types.NAVIGATE:
{
context = expression( descriptor.get(1) );
method = identifier( descriptor.get(2) );
safe = type == Types.NAVIGATE;
break;
}
default:
{
context = expression( descriptor );
break;
}
}
//
// And build the expression
Expression parameters = parameterList( reduction.get(2) );
// System.out.println( "method call expression: " + context + ", " + method + ", " + parameters + ", " + implicit );
call = new MethodCallExpression( context, method, parameters );
call.setImplicitThis( implicit );
call.setSafe( safe );
return call;
}
/**
* Processes the Reduction produced by Parser.closureExpression().
*/
protected ClosureExpression closureExpression( CSTNode reduction ) throws ParserException
{
ClosureExpression expression = null;
Parameter[] parameters = parameterDeclarations( reduction.get(1) );
expression = new ClosureExpression( parameters, statementBlock(reduction.get(2)) );
return expression;
}
/**
* Processes the Reduction produced by Parser.parameterList().
*/
protected Expression parameterList( CSTNode reduction ) throws ParserException
{
TupleExpression list = new TupleExpression();
for( int i = 1; i < reduction.size(); i++ )
{
CSTNode node = reduction.get(i);
list.addExpression( expression(node) );
}
return list;
}
/**
* Processes the Reduction produced by Parser.newExpression().
*/
protected Expression newExpression( CSTNode reduction ) throws ParserException
{
Expression expression = null;
CSTNode typeNode = reduction.get(1);
String type = resolveName( typeNode );
//
// Array types have dimension and initialization data to handle.
if( typeNode.getMeaning() == Types.LEFT_SQUARE_BRACKET )
{
CSTNode dimensions = reduction.get(2);
//
// BUG: at present, ArrayExpression expects a scalar type and
// does not support multi-dimensional arrays. In future, the
// the latter will need to change, and that may require the
// former to change, as well. For now, we calculate the scalar
// type and error for multiple dimensions.
if( typeNode.get(1).getMeaning() == Types.LEFT_SQUARE_BRACKET )
{
throw new GroovyBugError( "NOT YET IMPLEMENTED: multidimensional arrays" );
}
else
{
type = resolveName( typeNode.get(1) );
}
//
// If there are no dimensions, process a tuple initializer
if( dimensions.isEmpty() )
{
CSTNode data = reduction.get(3);
if( data.get(1, true).getMeaning() == Types.SYNTH_TUPLE )
{
throw new GroovyBugError( "NOT YET IMPLEMENTED: multidimensional arrays" );
}
expression = new ArrayExpression( type, tupleExpression(data).getExpressions() );
}
//
// Otherwise, process the dimensions
else
{
if( dimensions.size() > 2 )
{
throw new GroovyBugError( "NOT YET IMPLEMENTED: multidimensional arrays" );
/*
expression = new ArrayExpression( type, tupleExpression(dimensions) );
*/
}
else
{
expression = new ArrayExpression( type, expression(dimensions.get(1)) );
}
}
}
//
// Scalar types have a constructor parameter list and possibly a type body
else
{
Expression parameters = parameterList( reduction.get(2) );
if( reduction.size() > 3 )
{
throw new GroovyBugError( "NOT YET IMPLEMENTED: anonymous classes" );
}
expression = new ConstructorCallExpression( type, parameters );
}
return expression;
}
/**
* Processes the Reduction produced by Parser.newArrayInitializer().
*/
protected TupleExpression tupleExpression( CSTNode reduction ) throws ParserException
{
TupleExpression tuple = new TupleExpression();
for( int i = 1; i < reduction.size(); i++ )
{
CSTNode element = reduction.get(i);
if( element.getMeaning() == Types.SYNTH_TUPLE )
{
tuple.addExpression( tupleExpression(element) );
}
else
{
tuple.addExpression( expression(element) );
}
}
return tuple;
}
/**
* Processes the Reduction produced by Parser.gstring().
*/
protected Expression gstringExpression( CSTNode reduction ) throws ParserException
{
if( !reduction.hasChildren() )
{
return new ConstantExpression( "" );
}
if( reduction.children() == 1 && reduction.get(1).getMeaning() == Types.STRING )
{
return expression( reduction.get(1) );
}
GStringExpression expression = new GStringExpression( reduction.getRootText() );
boolean lastWasExpression = false;
for( int i = 1; i < reduction.size(); i++ )
{
CSTNode element = reduction.get(i);
if( element.getMeaning() == Types.STRING )
{
ConstantExpression string = new ConstantExpression( element.getRootText() );
string.setCSTNode( element );
expression.addString( string );
lastWasExpression = false;
}
else
{
if( lastWasExpression )
{
expression.addString( new ConstantExpression("") );
}
lastWasExpression = true;
expression.addValue( element.isEmpty() ? ConstantExpression.NULL : expression(element) );
}
}
return expression;
}
/**
* Processes one of the Reductions produced by Parser.listOrMapExpression().
*/
protected ListExpression listExpression( CSTNode reduction ) throws ParserException
{
ListExpression list = new ListExpression();
for( int i = 1; i < reduction.size(); i++ )
{
list.addExpression( expression(reduction.get(i)) );
}
return list;
}
/**
* Processes the other Reduction produced by Parser.listOrMapExpression().
*/
protected MapExpression mapExpression( CSTNode reduction ) throws ParserException
{
MapExpression map = new MapExpression();
for( int i = 1; i < reduction.size(); i++ )
{
CSTNode element = reduction.get(i);
Expression key = expression( element.get(1) );
Expression value = expression( element.get(2) );
map.addMapEntryExpression( key, value );
}
return map;
}
//---------------------------------------------------------------------------
// NAMING
/**
* Converts a CSTNode representation of a type name back into
* a string.
*/
protected String makeName( CSTNode root, String defaultName )
{
if( root == null )
{
return defaultName;
}
String name = "";
switch( root.getMeaning() )
{
case Types.LEFT_SQUARE_BRACKET:
{
name = makeName( root.get(1) ) + "[]";
break;
}
case Types.DOT:
{
CSTNode node = root;
while( node.isA(Types.DOT) )
{
name = "." + node.get(2).getRootText() + name;
node = node.get(1);
}
name = node.getRootText() + name;
break;
}
case Types.UNKNOWN:
{
name = defaultName;
break;
}
default:
{
name = root.getRootText();
break;
}
}
return name;
}
/**
* A synonym for <code>makeName( root, "java.lang.Object" )</code>.
*/
protected String makeName( CSTNode root )
{
return makeName( root, "" ); // br: the default name. was "java.lang.Object"
}
/**
* Returns the text of an identifier.
*/
protected String identifier( CSTNode identifier )
{
return identifier.getRootText();
}
/**
* Builds a name from a CSTNode, then resolves it. Returns the resolved name
* if available, or null, unless safe is set, in which case the built name
* is returned instead of null.
*
* @todo we should actually remove all resolving code from the ASTBuilder and
* move it into the verifier / analyser
*/
protected String resolveName( CSTNode root, boolean safe )
{
String name = makeName( root );
if (name.length() == 0)
return "";
return resolveNewClassOrName(name, safe);
}
/**
* A synonym for <code>resolveName( root, true )</code>.
*/
protected String resolveName( CSTNode root )
{
return resolveName( root, true );
}
//---------------------------------------------------------------------------
// ASM SUPPORT
/**
* Returns the ASM Constant bits for the specified modifiers.
*/
protected int modifiers( CSTNode list )
{
int modifiers = 0;
for( int i = 1; i < list.size(); ++i )
{
SWITCH: switch( list.get(i).getMeaning() )
{
case Types.KEYWORD_PUBLIC:
{
modifiers |= Constants.ACC_PUBLIC;
break SWITCH;
}
case Types.KEYWORD_PROTECTED:
{
modifiers |= Constants.ACC_PROTECTED;
break SWITCH;
}
case Types.KEYWORD_PRIVATE:
{
modifiers |= Constants.ACC_PRIVATE;
break SWITCH;
}
case Types.KEYWORD_ABSTRACT:
{
modifiers |= Constants.ACC_ABSTRACT;
break SWITCH;
}
case Types.KEYWORD_FINAL:
{
modifiers |= Constants.ACC_FINAL;
break SWITCH;
}
case Types.KEYWORD_NATIVE:
{
modifiers |= Constants.ACC_NATIVE;
break SWITCH;
}
case Types.KEYWORD_TRANSIENT:
{
modifiers |= Constants.ACC_TRANSIENT;
break SWITCH;
}
case Types.KEYWORD_VOLATILE:
{
modifiers |= Constants.ACC_VOLATILE;
break SWITCH;
}
case Types.KEYWORD_SYNCHRONIZED:
{
modifiers |= Constants.ACC_SYNCHRONIZED;
break SWITCH;
}
case Types.KEYWORD_STATIC:
{
modifiers |= Constants.ACC_STATIC;
break SWITCH;
}
}
}
//
// If not protected or private we default to public.
if( (modifiers & (Constants.ACC_PROTECTED | Constants.ACC_PRIVATE)) == 0 )
{
modifiers |= Constants.ACC_PUBLIC;
}
return modifiers;
}
//---------------------------------------------------------------------------
// ERROR HANDLING
/**
* Throws a <code>ParserException</code>.
*/
protected void error( String description, CSTNode node ) throws ParserException
{
throw new ParserException( description, node.getRoot() );
}
}