blob: 5ebac874c797f1b0c46c4d6070e6b4375e6147ac [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.codehaus.groovy.transform.tailrec
import groovy.transform.CompileStatic
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.expr.BinaryExpression
import org.codehaus.groovy.ast.expr.Expression
import org.codehaus.groovy.ast.expr.MethodCallExpression
import org.codehaus.groovy.ast.expr.StaticMethodCallExpression
import org.codehaus.groovy.ast.expr.TupleExpression
import org.codehaus.groovy.ast.expr.VariableExpression
import org.codehaus.groovy.ast.stmt.BlockStatement
import org.codehaus.groovy.ast.stmt.ExpressionStatement
import org.codehaus.groovy.ast.stmt.ReturnStatement
import org.codehaus.groovy.ast.stmt.Statement
import static org.codehaus.groovy.ast.tools.GeneralUtils.assignS
import static org.codehaus.groovy.ast.tools.GeneralUtils.varX
/**
* Translates all return statements into an invocation of the next iteration. This can be either
* - "continue LOOP_LABEL": Outside closures
* - "throw LOOP_EXCEPTION": Inside closures
*
* Moreover, before adding the recur statement the iteration parameters (originally the method args)
* are set to their new value. To prevent variable aliasing parameters will be copied into temp vars
* before they are changes so that their current iteration value can be used when setting other params.
*
* There's probably place for optimizing the amount of variable copying being done, e.g.
* parameters that are only handed through must not be copied at all.
*/
@CompileStatic
class ReturnStatementToIterationConverter {
Statement recurStatement = AstHelper.recurStatement()
Statement convert(ReturnStatement statement, Map<Integer, Map> positionMapping) {
Expression recursiveCall = statement.expression
if (!isAMethodCalls(recursiveCall))
return statement
Map<String, Map> tempMapping = [:]
Map tempDeclarations = [:]
List<ExpressionStatement> argAssignments = []
BlockStatement result = new BlockStatement()
result.copyStatementLabels(statement)
/* Create temp declarations for all method arguments.
* Add the declarations and var mapping to tempMapping and tempDeclarations for further reference.
*/
getArguments(recursiveCall).eachWithIndex { Expression expression, int index ->
ExpressionStatement tempDeclaration = createTempDeclaration(index, positionMapping, tempMapping, tempDeclarations)
result.addStatement(tempDeclaration)
}
/*
* Assign the iteration variables their new value before recuring
*/
getArguments(recursiveCall).eachWithIndex { Expression expression, int index ->
ExpressionStatement argAssignment = createAssignmentToIterationVariable(expression, index, positionMapping)
argAssignments.add(argAssignment)
result.addStatement(argAssignment)
}
Set<String> unusedTemps = replaceAllArgUsages(argAssignments, tempMapping)
for (String temp : unusedTemps) {
result.statements.remove(tempDeclarations[temp])
}
result.addStatement(recurStatement)
result
}
private ExpressionStatement createAssignmentToIterationVariable(Expression expression, int index, Map<Integer, Map> positionMapping) {
String argName = positionMapping[index]['name']
ClassNode argAndTempType = positionMapping[index]['type'] as ClassNode
ExpressionStatement argAssignment = (ExpressionStatement) assignS(varX(argName, argAndTempType), expression)
argAssignment
}
private ExpressionStatement createTempDeclaration(int index, Map<Integer, Map> positionMapping, Map<String, Map> tempMapping, Map tempDeclarations) {
String argName = positionMapping[index]['name']
String tempName = "_${argName}_"
ClassNode argAndTempType = positionMapping[index]['type'] as ClassNode
ExpressionStatement tempDeclaration = AstHelper.createVariableAlias(tempName, argAndTempType, argName)
tempMapping[argName] = [name: tempName, type: argAndTempType]
tempDeclarations[tempName] = tempDeclaration
tempDeclaration
}
@SuppressWarnings('Instanceof')
private List<Expression> getArguments(Expression recursiveCall) {
if (recursiveCall instanceof MethodCallExpression)
return ((TupleExpression) ((MethodCallExpression) recursiveCall).arguments).expressions
if (recursiveCall instanceof StaticMethodCallExpression)
return ((TupleExpression) ((StaticMethodCallExpression) recursiveCall).arguments).expressions
}
private boolean isAMethodCalls(Expression expression) {
expression.class in [MethodCallExpression, StaticMethodCallExpression]
}
private Set<String> replaceAllArgUsages(List<ExpressionStatement> iterationVariablesAssignmentNodes, Map<String, Map> tempMapping) {
Set<String> unusedTempNames = tempMapping.values().collect { Map nameAndType -> (String) nameAndType['name'] } as Set<String>
VariableReplacedListener tracker = new UsedVariableTracker()
for (ExpressionStatement statement : iterationVariablesAssignmentNodes) {
replaceArgUsageByTempUsage((BinaryExpression) statement.expression, tempMapping, tracker)
}
unusedTempNames = unusedTempNames - tracker.usedVariableNames
unusedTempNames
}
private void replaceArgUsageByTempUsage(BinaryExpression binary, Map tempMapping, UsedVariableTracker tracker) {
VariableAccessReplacer replacer = new VariableAccessReplacer(nameAndTypeMapping: tempMapping, listener: tracker)
// Replacement must only happen in binary.rightExpression. It's a hack in VariableExpressionReplacer which takes care of that.
replacer.replaceIn(binary)
}
}
@CompileStatic
class UsedVariableTracker implements VariableReplacedListener {
final Set<String> usedVariableNames = [] as Set
@Override
void variableReplaced(VariableExpression oldVar, VariableExpression newVar) {
usedVariableNames.add(newVar.name)
}
}