This is an automated email from the ASF dual-hosted git repository. sunlan pushed a commit to branch danielsun/rewrite-gsrc-to-jsrc in repository https://gitbox.apache.org/repos/asf/groovy.git
commit 91c0a45e9fdc47b9f882193c5e8a0180e3551b14 Author: Daniel Sun <[email protected]> AuthorDate: Sat Aug 14 14:00:57 2021 +0800 Rewrite Groovy source code in core to Java --- .../groovy/transform/tailrec/AstHelper.groovy | 75 -------- .../groovy/transform/tailrec/AstHelper.java | 78 ++++++++ .../transform/tailrec/CollectRecursiveCalls.groovy | 60 ------ .../transform/tailrec/CollectRecursiveCalls.java | 64 ++++++ .../transform/tailrec/GotoRecurHereException.java | 26 +++ .../transform/tailrec/HasRecursiveCalls.groovy | 61 ------ .../transform/tailrec/HasRecursiveCalls.java | 63 ++++++ .../transform/tailrec/InWhileLoopWrapper.groovy | 83 -------- .../transform/tailrec/InWhileLoopWrapper.java | 60 ++++++ .../transform/tailrec/RecursivenessTester.groovy | 101 ---------- .../transform/tailrec/RecursivenessTester.java | 121 ++++++++++++ ...Closures.groovy => ReturnAdderForClosures.java} | 31 ++- .../transform/tailrec/StatementReplacer.groovy | 106 ---------- .../transform/tailrec/StatementReplacer.java | 169 ++++++++++++++++ .../tailrec/VariableExpressionReplacer.groovy | 163 ---------------- .../tailrec/VariableExpressionReplacer.java | 214 +++++++++++++++++++++ .../tailrec/VariableExpressionTransformer.groovy | 46 ----- .../tailrec/VariableExpressionTransformer.java | 66 +++++++ 18 files changed, 876 insertions(+), 711 deletions(-) diff --git a/src/main/groovy/org/codehaus/groovy/transform/tailrec/AstHelper.groovy b/src/main/groovy/org/codehaus/groovy/transform/tailrec/AstHelper.groovy deleted file mode 100644 index 35cf13f..0000000 --- a/src/main/groovy/org/codehaus/groovy/transform/tailrec/AstHelper.groovy +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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.Expression -import org.codehaus.groovy.ast.expr.VariableExpression -import org.codehaus.groovy.ast.stmt.ContinueStatement -import org.codehaus.groovy.ast.stmt.ExpressionStatement -import org.codehaus.groovy.ast.stmt.Statement - -import java.lang.reflect.Modifier - -import static org.codehaus.groovy.ast.tools.GeneralUtils.classX -import static org.codehaus.groovy.ast.tools.GeneralUtils.declS -import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX -import static org.codehaus.groovy.ast.tools.GeneralUtils.propX -import static org.codehaus.groovy.ast.tools.GeneralUtils.throwS -import static org.codehaus.groovy.ast.tools.GeneralUtils.varX - -/** - * Helping to create a few standard AST constructs - */ -@CompileStatic -class AstHelper { - static ExpressionStatement createVariableDefinition(String variableName, ClassNode variableType, Expression value, boolean variableShouldBeFinal = false) { - def newVariable = localVarX(variableName, variableType) - if (variableShouldBeFinal) - newVariable.modifiers = Modifier.FINAL - (ExpressionStatement) declS(newVariable, value) - } - - static ExpressionStatement createVariableAlias(String aliasName, ClassNode variableType, String variableName) { - createVariableDefinition(aliasName, variableType, varX(variableName, variableType)) - } - - static VariableExpression createVariableReference(Map variableSpec) { - varX((String) variableSpec.name, (ClassNode) variableSpec.type) - } - - /** - * This statement should make the code jump to surrounding while loop's start label - * Does not work from within Closures - */ - static Statement recurStatement() { - //continue _RECUR_HERE_ - new ContinueStatement(InWhileLoopWrapper.LOOP_LABEL) - } - - /** - * This statement will throw exception which will be caught and redirected to jump to surrounding while loop's start label - * Also works from within Closures but is a tiny bit slower - */ - static Statement recurByThrowStatement() { - // throw InWhileLoopWrapper.LOOP_EXCEPTION - throwS(propX(classX(InWhileLoopWrapper), 'LOOP_EXCEPTION')) - } -} diff --git a/src/main/groovy/org/codehaus/groovy/transform/tailrec/AstHelper.java b/src/main/groovy/org/codehaus/groovy/transform/tailrec/AstHelper.java new file mode 100644 index 0000000..fc09f6c --- /dev/null +++ b/src/main/groovy/org/codehaus/groovy/transform/tailrec/AstHelper.java @@ -0,0 +1,78 @@ +/* + * 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 org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.VariableExpression; +import org.codehaus.groovy.ast.stmt.ContinueStatement; +import org.codehaus.groovy.ast.stmt.ExpressionStatement; +import org.codehaus.groovy.ast.stmt.Statement; + +import java.lang.reflect.Modifier; +import java.util.Map; + +import static org.codehaus.groovy.ast.tools.GeneralUtils.classX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.declS; +import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.propX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.throwS; +import static org.codehaus.groovy.ast.tools.GeneralUtils.varX; + +/** + * Helping to create a few standard AST constructs + */ +class AstHelper { + public static ExpressionStatement createVariableDefinition(String variableName, ClassNode variableType, Expression value) { + return createVariableDefinition(variableName, variableType, value, false); + } + + public static ExpressionStatement createVariableDefinition(String variableName, ClassNode variableType, Expression value, boolean variableShouldBeFinal) { + VariableExpression newVariable = localVarX(variableName, variableType); + if (variableShouldBeFinal) + newVariable.setModifiers(Modifier.FINAL); + return (ExpressionStatement) declS(newVariable, value); + } + + public static ExpressionStatement createVariableAlias(String aliasName, ClassNode variableType, String variableName) { + return createVariableDefinition(aliasName, variableType, varX(variableName, variableType)); + } + + public static VariableExpression createVariableReference(Map<String, ?> variableSpec) { + return varX((String) variableSpec.get("name"), (ClassNode) variableSpec.get("type")); + } + + /** + * This statement should make the code jump to surrounding while loop's start label + * Does not work from within Closures + */ + public static Statement recurStatement() { + //continue _RECUR_HERE_ + return new ContinueStatement(InWhileLoopWrapper.LOOP_LABEL); + } + + /** + * This statement will throw exception which will be caught and redirected to jump to surrounding while loop's start label + * Also works from within Closures but is a tiny bit slower + */ + public static Statement recurByThrowStatement() { + // throw InWhileLoopWrapper.LOOP_EXCEPTION + return throwS(propX(classX(InWhileLoopWrapper.class), "LOOP_EXCEPTION")); + } +} diff --git a/src/main/groovy/org/codehaus/groovy/transform/tailrec/CollectRecursiveCalls.groovy b/src/main/groovy/org/codehaus/groovy/transform/tailrec/CollectRecursiveCalls.groovy deleted file mode 100644 index 3f586d8..0000000 --- a/src/main/groovy/org/codehaus/groovy/transform/tailrec/CollectRecursiveCalls.groovy +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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.CodeVisitorSupport -import org.codehaus.groovy.ast.MethodNode -import org.codehaus.groovy.ast.expr.Expression -import org.codehaus.groovy.ast.expr.MethodCallExpression -import org.codehaus.groovy.ast.expr.StaticMethodCallExpression - -/** - * Collect all recursive calls within method - */ -@CompileStatic -class CollectRecursiveCalls extends CodeVisitorSupport { - MethodNode method - List<Expression> recursiveCalls = [] - - void visitMethodCallExpression(MethodCallExpression call) { - if (isRecursive(call)) { - recursiveCalls << call - } - super.visitMethodCallExpression(call) - } - - void visitStaticMethodCallExpression(StaticMethodCallExpression call) { - if (isRecursive(call)) { - recursiveCalls << call - } - super.visitStaticMethodCallExpression(call) - } - - private boolean isRecursive(call) { - new RecursivenessTester().isRecursive(method: method, call: call) - } - - synchronized List<Expression> collect(MethodNode method) { - recursiveCalls.clear() - this.method = method - this.method.code.visit(this) - recursiveCalls - } -} diff --git a/src/main/groovy/org/codehaus/groovy/transform/tailrec/CollectRecursiveCalls.java b/src/main/groovy/org/codehaus/groovy/transform/tailrec/CollectRecursiveCalls.java new file mode 100644 index 0000000..e996a86 --- /dev/null +++ b/src/main/groovy/org/codehaus/groovy/transform/tailrec/CollectRecursiveCalls.java @@ -0,0 +1,64 @@ +/* + * 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 org.apache.groovy.util.Maps; +import org.codehaus.groovy.ast.CodeVisitorSupport; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.ast.expr.StaticMethodCallExpression; + +import java.util.ArrayList; +import java.util.List; + +/** + * Collect all recursive calls within method + */ +class CollectRecursiveCalls extends CodeVisitorSupport { + private final List<Expression> recursiveCalls = new ArrayList<>(); + private MethodNode method; + + @Override + public void visitMethodCallExpression(MethodCallExpression call) { + if (isRecursive(call)) { + recursiveCalls.add(call); + } + super.visitMethodCallExpression(call); + } + + @Override + public void visitStaticMethodCallExpression(StaticMethodCallExpression call) { + if (isRecursive(call)) { + recursiveCalls.add(call); + } + super.visitStaticMethodCallExpression(call); + } + + public synchronized List<Expression> collect(MethodNode method) { + recursiveCalls.clear(); + this.method = method; + this.method.getCode().visit(this); + return recursiveCalls; + } + + private boolean isRecursive(Expression call) { + return new RecursivenessTester().isRecursive(Maps.of("method", method, "call", call)); + } +} diff --git a/src/main/groovy/org/codehaus/groovy/transform/tailrec/GotoRecurHereException.java b/src/main/groovy/org/codehaus/groovy/transform/tailrec/GotoRecurHereException.java new file mode 100644 index 0000000..54375ca --- /dev/null +++ b/src/main/groovy/org/codehaus/groovy/transform/tailrec/GotoRecurHereException.java @@ -0,0 +1,26 @@ +/* + * 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; + +/** + * Exception will be thrown by recursive calls in closures and caught in while loop to continue to LOOP_LABEL + */ +public class GotoRecurHereException extends Exception { + private static final long serialVersionUID = -193137033604506378L; +} diff --git a/src/main/groovy/org/codehaus/groovy/transform/tailrec/HasRecursiveCalls.groovy b/src/main/groovy/org/codehaus/groovy/transform/tailrec/HasRecursiveCalls.groovy deleted file mode 100644 index e8799c3..0000000 --- a/src/main/groovy/org/codehaus/groovy/transform/tailrec/HasRecursiveCalls.groovy +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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.CodeVisitorSupport -import org.codehaus.groovy.ast.MethodNode -import org.codehaus.groovy.ast.expr.MethodCallExpression -import org.codehaus.groovy.ast.expr.StaticMethodCallExpression - -/** - * Check if there are any recursive calls in a method - */ -@CompileStatic -class HasRecursiveCalls extends CodeVisitorSupport { - MethodNode method - boolean hasRecursiveCalls = false - - void visitMethodCallExpression(MethodCallExpression call) { - if (isRecursive(call)) { - hasRecursiveCalls = true - } else { - super.visitMethodCallExpression(call) - } - } - - void visitStaticMethodCallExpression(StaticMethodCallExpression call) { - if (isRecursive(call)) { - hasRecursiveCalls = true - } else { - super.visitStaticMethodCallExpression(call) - } - } - - private boolean isRecursive(call) { - new RecursivenessTester().isRecursive(method: method, call: call) - } - - synchronized boolean test(MethodNode method) { - hasRecursiveCalls = false - this.method = method - this.method.code.visit(this) - hasRecursiveCalls - } -} \ No newline at end of file diff --git a/src/main/groovy/org/codehaus/groovy/transform/tailrec/HasRecursiveCalls.java b/src/main/groovy/org/codehaus/groovy/transform/tailrec/HasRecursiveCalls.java new file mode 100644 index 0000000..9042ed2 --- /dev/null +++ b/src/main/groovy/org/codehaus/groovy/transform/tailrec/HasRecursiveCalls.java @@ -0,0 +1,63 @@ +/* + * 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 org.apache.groovy.util.Maps; +import org.codehaus.groovy.ast.CodeVisitorSupport; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.ast.expr.StaticMethodCallExpression; + +/** + * Check if there are any recursive calls in a method + */ +class HasRecursiveCalls extends CodeVisitorSupport { + private MethodNode method; + private boolean hasRecursiveCalls = false; + + @Override + public void visitMethodCallExpression(MethodCallExpression call) { + if (isRecursive(call)) { + hasRecursiveCalls = true; + } else { + super.visitMethodCallExpression(call); + } + } + + @Override + public void visitStaticMethodCallExpression(StaticMethodCallExpression call) { + if (isRecursive(call)) { + hasRecursiveCalls = true; + } else { + super.visitStaticMethodCallExpression(call); + } + } + + public synchronized boolean test(MethodNode method) { + hasRecursiveCalls = false; + this.method = method; + this.method.getCode().visit(this); + return hasRecursiveCalls; + } + + private boolean isRecursive(Expression call) { + return new RecursivenessTester().isRecursive(Maps.of("method", method, "call", call)); + } +} diff --git a/src/main/groovy/org/codehaus/groovy/transform/tailrec/InWhileLoopWrapper.groovy b/src/main/groovy/org/codehaus/groovy/transform/tailrec/InWhileLoopWrapper.groovy deleted file mode 100644 index d622dc5..0000000 --- a/src/main/groovy/org/codehaus/groovy/transform/tailrec/InWhileLoopWrapper.groovy +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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.ClassHelper -import org.codehaus.groovy.ast.MethodNode -import org.codehaus.groovy.ast.VariableScope -import org.codehaus.groovy.ast.stmt.BlockStatement -import org.codehaus.groovy.ast.stmt.ContinueStatement -import org.codehaus.groovy.ast.stmt.EmptyStatement -import org.codehaus.groovy.ast.stmt.Statement -import org.codehaus.groovy.ast.stmt.TryCatchStatement -import org.codehaus.groovy.ast.stmt.WhileStatement - -import static org.codehaus.groovy.ast.tools.GeneralUtils.block -import static org.codehaus.groovy.ast.tools.GeneralUtils.boolX -import static org.codehaus.groovy.ast.tools.GeneralUtils.catchS -import static org.codehaus.groovy.ast.tools.GeneralUtils.constX -import static org.codehaus.groovy.ast.tools.GeneralUtils.param -import static org.codehaus.groovy.ast.tools.GeneralUtils.tryCatchS - -/** - * Wrap the body of a method in a while loop, nested in a try-catch. - * This is the first step in making a tail recursive method iterative. - * - * There are two ways to invoke the next iteration step: - * <ol> - * <li>"continue _RECUR_HERE_" is used by recursive calls outside of closures</li> - * <li>"throw LOOP_EXCEPTION" is used by recursive calls within closures b/c you cannot invoke "continue" from there</li> - * </ol> - */ -@CompileStatic -class InWhileLoopWrapper { - static final String LOOP_LABEL = '_RECUR_HERE_' - static final GotoRecurHereException LOOP_EXCEPTION = new GotoRecurHereException() - - void wrap(MethodNode method) { - BlockStatement oldBody = method.code as BlockStatement - TryCatchStatement tryCatchStatement = tryCatchS( - oldBody, - EmptyStatement.INSTANCE, - catchS( - param(ClassHelper.make(GotoRecurHereException), 'ignore'), - new ContinueStatement(InWhileLoopWrapper.LOOP_LABEL) - )) - - WhileStatement whileLoop = new WhileStatement( - boolX(constX(true)), - block(new VariableScope(method.variableScope), tryCatchStatement) - ) - List<Statement> whileLoopStatements = ((BlockStatement) whileLoop.loopBlock).statements - if (whileLoopStatements.size() > 0) - whileLoopStatements[0].statementLabel = LOOP_LABEL - BlockStatement newBody = block(new VariableScope(method.variableScope)) - newBody.addStatement(whileLoop) - method.code = newBody - } -} - -/** - * Exception will be thrown by recursive calls in closures and caught in while loop to continue to LOOP_LABEL - */ -@CompileStatic -class GotoRecurHereException extends Exception { - private static final long serialVersionUID = -193137033604506378L -} diff --git a/src/main/groovy/org/codehaus/groovy/transform/tailrec/InWhileLoopWrapper.java b/src/main/groovy/org/codehaus/groovy/transform/tailrec/InWhileLoopWrapper.java new file mode 100644 index 0000000..a4d8f09 --- /dev/null +++ b/src/main/groovy/org/codehaus/groovy/transform/tailrec/InWhileLoopWrapper.java @@ -0,0 +1,60 @@ +/* + * 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 org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.VariableScope; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.ast.stmt.ContinueStatement; +import org.codehaus.groovy.ast.stmt.EmptyStatement; +import org.codehaus.groovy.ast.stmt.Statement; +import org.codehaus.groovy.ast.stmt.TryCatchStatement; +import org.codehaus.groovy.ast.stmt.WhileStatement; +import org.codehaus.groovy.ast.tools.GeneralUtils; +import org.codehaus.groovy.runtime.DefaultGroovyMethods; + +import java.util.List; + +/** + * Wrap the body of a method in a while loop, nested in a try-catch. + * This is the first step in making a tail recursive method iterative. + * <p> + * There are two ways to invoke the next iteration step: + * <ol> + * <li>"continue _RECUR_HERE_" is used by recursive calls outside of closures</li> + * <li>"throw LOOP_EXCEPTION" is used by recursive calls within closures b/c you cannot invoke "continue" from there</li> + * </ol> + */ +class InWhileLoopWrapper { + public void wrap(MethodNode method) { + BlockStatement oldBody = DefaultGroovyMethods.asType(method.getCode(), BlockStatement.class); + TryCatchStatement tryCatchStatement = GeneralUtils.tryCatchS(oldBody, EmptyStatement.INSTANCE, GeneralUtils.catchS(GeneralUtils.param(ClassHelper.make(GotoRecurHereException.class), "ignore"), new ContinueStatement(InWhileLoopWrapper.LOOP_LABEL))); + + WhileStatement whileLoop = new WhileStatement(GeneralUtils.boolX(GeneralUtils.constX(true)), GeneralUtils.block(new VariableScope(method.getVariableScope()), tryCatchStatement)); + List<Statement> whileLoopStatements = ((BlockStatement) whileLoop.getLoopBlock()).getStatements(); + if (whileLoopStatements.size() > 0) whileLoopStatements.get(0).setStatementLabel(LOOP_LABEL); + BlockStatement newBody = GeneralUtils.block(new VariableScope(method.getVariableScope())); + newBody.addStatement(whileLoop); + method.setCode(newBody); + } + + public static final String LOOP_LABEL = "_RECUR_HERE_"; + public static final GotoRecurHereException LOOP_EXCEPTION = new GotoRecurHereException(); +} diff --git a/src/main/groovy/org/codehaus/groovy/transform/tailrec/RecursivenessTester.groovy b/src/main/groovy/org/codehaus/groovy/transform/tailrec/RecursivenessTester.groovy deleted file mode 100644 index 15e24e4..0000000 --- a/src/main/groovy/org/codehaus/groovy/transform/tailrec/RecursivenessTester.groovy +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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 org.codehaus.groovy.ast.ClassHelper -import org.codehaus.groovy.ast.ClassNode -import org.codehaus.groovy.ast.MethodNode -import org.codehaus.groovy.ast.expr.ConstantExpression -import org.codehaus.groovy.ast.expr.MethodCallExpression -import org.codehaus.groovy.ast.expr.StaticMethodCallExpression -import org.codehaus.groovy.ast.expr.VariableExpression - -/** - * Test if a method call is recursive if called within a given method node. - * Handles static calls as well. - * - * Currently known simplifications: - * <ul> - * <li>Does not check for method overloading or overridden methods</li> - * <li>Does not check for matching return types; even void and any object type are considered to be compatible</li> - * <li>Argument type matching could be more specific in case of static compilation</li> - * <li>Method names via a GString are never considered to be recursive</li> - * </ul> - */ -class RecursivenessTester { - boolean isRecursive(params) { - assert params.method.class == MethodNode - assert params.call.class == MethodCallExpression || StaticMethodCallExpression - - isRecursive(params.method, params.call) - } - - @SuppressWarnings('Instanceof') - boolean isRecursive(MethodNode method, MethodCallExpression call) { - if (!isCallToThis(call)) - return false - // Could be a GStringExpression - if (! (call.method instanceof ConstantExpression)) - return false - if (call.method.value != method.name) - return false - methodParamsMatchCallArgs(method, call) - } - - boolean isRecursive(MethodNode method, StaticMethodCallExpression call) { - if (!method.isStatic()) - return false - if (method.declaringClass != call.ownerType) - return false - if (call.method != method.name) - return false - methodParamsMatchCallArgs(method, call) - } - - @SuppressWarnings('Instanceof') - private boolean isCallToThis(MethodCallExpression call) { - if (call.objectExpression == null) - return call.isImplicitThis() - if (! (call.objectExpression instanceof VariableExpression)) { - return false - } - call.objectExpression.isThisExpression() - } - - private boolean methodParamsMatchCallArgs(method, call) { - if (method.parameters.size() != call.arguments.expressions.size()) - return false - def classNodePairs = [method.parameters*.type, call.arguments*.type].transpose() - classNodePairs.every { ClassNode paramType, ClassNode argType -> - areTypesCallCompatible(argType, paramType) - } - } - - /** - * Parameter type and calling argument type can both be derived from the other since typing information is - * optional in Groovy. - * Since int is not derived from Integer (nor the other way around) we compare the boxed types - */ - private areTypesCallCompatible(ClassNode argType, ClassNode paramType) { - ClassNode boxedArg = ClassHelper.getWrapper(argType) - ClassNode boxedParam = ClassHelper.getWrapper(paramType) - boxedArg.isDerivedFrom(boxedParam) || boxedParam.isDerivedFrom(boxedArg) - } - -} diff --git a/src/main/groovy/org/codehaus/groovy/transform/tailrec/RecursivenessTester.java b/src/main/groovy/org/codehaus/groovy/transform/tailrec/RecursivenessTester.java new file mode 100644 index 0000000..d88d88e --- /dev/null +++ b/src/main/groovy/org/codehaus/groovy/transform/tailrec/RecursivenessTester.java @@ -0,0 +1,121 @@ +/* + * 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 org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.expr.ConstantExpression; +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.runtime.DefaultGroovyMethods; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.codehaus.groovy.runtime.DefaultGroovyMethods.transpose; + +/** + * Test if a method call is recursive if called within a given method node. + * Handles static calls as well. + * <p> + * Currently known simplifications: + * <ul> + * <li>Does not check for method overloading or overridden methods</li> + * <li>Does not check for matching return types; even void and any object type are considered to be compatible</li> + * <li>Argument type matching could be more specific in case of static compilation</li> + * <li>Method names via a GString are never considered to be recursive</li> + * </ul> + */ +class RecursivenessTester { + public boolean isRecursive(Map<String, ASTNode> params) { + ASTNode method = params.get("method"); + assert MethodNode.class.equals(method.getClass()); + ASTNode call = params.get("call"); + Class<? extends ASTNode> callClass = call.getClass(); + assert MethodCallExpression.class.equals(callClass) || StaticMethodCallExpression.class.equals(callClass); + + if (callClass == MethodCallExpression.class) { + return isRecursive((MethodNode) method, (MethodCallExpression) call); + } + return isRecursive((MethodNode) method, (StaticMethodCallExpression) call); + } + + @SuppressWarnings("Instanceof") + public boolean isRecursive(MethodNode method, MethodCallExpression call) { + if (!isCallToThis(call)) return false; + // Could be a GStringExpression + if (!(call.getMethod() instanceof ConstantExpression)) return false; + if (!((ConstantExpression) call.getMethod()).getValue().equals(method.getName())) return false; + return methodParamsMatchCallArgs(method, call); + } + + public boolean isRecursive(MethodNode method, StaticMethodCallExpression call) { + if (!method.isStatic()) return false; + if (!method.getDeclaringClass().equals(call.getOwnerType())) return false; + if (!call.getMethod().equals(method.getName())) return false; + return methodParamsMatchCallArgs(method, call); + } + + @SuppressWarnings("Instanceof") + private boolean isCallToThis(MethodCallExpression call) { + if (call.getObjectExpression() == null) return call.isImplicitThis(); + if (!(call.getObjectExpression() instanceof VariableExpression)) { + return false; + } + + return ((boolean) (DefaultGroovyMethods.invokeMethod(call.getObjectExpression(), "isThisExpression", new Object[0]))); + } + + private boolean methodParamsMatchCallArgs(MethodNode method, Expression call) { + TupleExpression arguments; + if (call instanceof MethodCallExpression) { + arguments = ((TupleExpression) ((MethodCallExpression) call).getArguments()); + } else { + arguments = ((TupleExpression) ((StaticMethodCallExpression) call).getArguments()); + } + + if (method.getParameters().length != arguments.getExpressions().size()) + return false; + + List<List<ClassNode>> classNodePairs = + transpose(Arrays.asList( + Arrays.stream(method.getParameters()).map(Parameter::getType).collect(Collectors.toList()), + arguments.getExpressions().stream().map(Expression::getType).collect(Collectors.toList()))); + return classNodePairs.stream().allMatch(t -> areTypesCallCompatible(t.get(0), t.get(1))); + } + + /** + * Parameter type and calling argument type can both be derived from the other since typing information is + * optional in Groovy. + * Since int is not derived from Integer (nor the other way around) we compare the boxed types + */ + private Boolean areTypesCallCompatible(ClassNode argType, ClassNode paramType) { + ClassNode boxedArg = ClassHelper.getWrapper(argType); + ClassNode boxedParam = ClassHelper.getWrapper(paramType); + return boxedArg.isDerivedFrom(boxedParam) || boxedParam.isDerivedFrom(boxedArg); + } +} diff --git a/src/main/groovy/org/codehaus/groovy/transform/tailrec/ReturnAdderForClosures.groovy b/src/main/groovy/org/codehaus/groovy/transform/tailrec/ReturnAdderForClosures.java similarity index 63% rename from src/main/groovy/org/codehaus/groovy/transform/tailrec/ReturnAdderForClosures.groovy rename to src/main/groovy/org/codehaus/groovy/transform/tailrec/ReturnAdderForClosures.java index 62c4aa9..2dc6196 100644 --- a/src/main/groovy/org/codehaus/groovy/transform/tailrec/ReturnAdderForClosures.groovy +++ b/src/main/groovy/org/codehaus/groovy/transform/tailrec/ReturnAdderForClosures.java @@ -16,31 +16,30 @@ * specific language governing permissions and limitations * under the License. */ -package org.codehaus.groovy.transform.tailrec +package org.codehaus.groovy.transform.tailrec; -import org.codehaus.groovy.ast.ClassHelper -import org.codehaus.groovy.ast.ClassNode -import org.codehaus.groovy.ast.CodeVisitorSupport -import org.codehaus.groovy.ast.MethodNode -import org.codehaus.groovy.ast.Parameter -import org.codehaus.groovy.ast.expr.ClosureExpression -import org.codehaus.groovy.classgen.ReturnAdder +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.CodeVisitorSupport; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.expr.ClosureExpression; +import org.codehaus.groovy.classgen.ReturnAdder; /** * Adds explicit return statements to implicit return points in a closure. This is necessary since * tail-recursion is detected by having the recursive call within the return statement. */ class ReturnAdderForClosures extends CodeVisitorSupport { - - synchronized void visitMethod(MethodNode method) { - method.code.visit(this) + public synchronized void visitMethod(MethodNode method) { + method.getCode().visit(this); } - void visitClosureExpression(ClosureExpression expression) { + @Override + public void visitClosureExpression(ClosureExpression expression) { //Create a dummy method with the closure's code as the method's code. Then user ReturnAdder, which only works for methods. - MethodNode node = new MethodNode('dummy', 0, ClassHelper.OBJECT_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, expression.code) - new ReturnAdder().visitMethod(node) - super.visitClosureExpression(expression) + MethodNode node = new MethodNode("dummy", 0, ClassHelper.OBJECT_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, expression.getCode()); + new ReturnAdder().visitMethod(node); + super.visitClosureExpression(expression); } - } diff --git a/src/main/groovy/org/codehaus/groovy/transform/tailrec/StatementReplacer.groovy b/src/main/groovy/org/codehaus/groovy/transform/tailrec/StatementReplacer.groovy deleted file mode 100644 index 8c7a55a..0000000 --- a/src/main/groovy/org/codehaus/groovy/transform/tailrec/StatementReplacer.groovy +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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.ASTNode -import org.codehaus.groovy.ast.CodeVisitorSupport -import org.codehaus.groovy.ast.expr.ClosureExpression -import org.codehaus.groovy.ast.stmt.BlockStatement -import org.codehaus.groovy.ast.stmt.DoWhileStatement -import org.codehaus.groovy.ast.stmt.ForStatement -import org.codehaus.groovy.ast.stmt.IfStatement -import org.codehaus.groovy.ast.stmt.Statement -import org.codehaus.groovy.ast.stmt.WhileStatement - -/** - * Tool for replacing Statement objects in an AST by other Statement instances. - * - * Within @TailRecursive it is used to swap ReturnStatements with looping back to RECUR label - */ -@CompileStatic -class StatementReplacer extends CodeVisitorSupport { - - Closure<Boolean> when = { Statement node -> false } - Closure<Statement> replaceWith = { Statement statement -> statement } - int closureLevel = 0 - - void replaceIn(ASTNode root) { - root.visit(this) - } - - void visitClosureExpression(ClosureExpression expression) { - closureLevel++ - try { - super.visitClosureExpression(expression) - } finally { - closureLevel-- - } - } - - void visitBlockStatement(BlockStatement block) { - List<Statement> copyOfStatements = new ArrayList<Statement>(block.statements) - copyOfStatements.eachWithIndex { Statement statement, int index -> - replaceIfNecessary(statement) { Statement node -> block.statements[index] = node } - } - super.visitBlockStatement(block) - } - - void visitIfElse(IfStatement ifElse) { - replaceIfNecessary(ifElse.ifBlock) { Statement s -> ifElse.ifBlock = s } - replaceIfNecessary(ifElse.elseBlock) { Statement s -> ifElse.elseBlock = s } - super.visitIfElse(ifElse) - } - - void visitForLoop(ForStatement forLoop) { - replaceIfNecessary(forLoop.loopBlock) { Statement s -> forLoop.loopBlock = s } - super.visitForLoop(forLoop) - } - - void visitWhileLoop(WhileStatement loop) { - replaceIfNecessary(loop.loopBlock) { Statement s -> loop.loopBlock = s } - super.visitWhileLoop(loop) - } - - void visitDoWhileLoop(DoWhileStatement loop) { - replaceIfNecessary(loop.loopBlock) { Statement s -> loop.loopBlock = s } - super.visitDoWhileLoop(loop) - } - - - private void replaceIfNecessary(Statement nodeToCheck, Closure replacementCode) { - if (conditionFulfilled(nodeToCheck)) { - ASTNode replacement = replaceWith(nodeToCheck) - replacement.sourcePosition = nodeToCheck - replacement.copyNodeMetaData(nodeToCheck) - replacementCode(replacement) - } - } - - private boolean conditionFulfilled(ASTNode nodeToCheck) { - if (when.maximumNumberOfParameters < 2) - return when(nodeToCheck) - when(nodeToCheck, isInClosure()) - } - - private boolean isInClosure() { - closureLevel > 0 - } - -} diff --git a/src/main/groovy/org/codehaus/groovy/transform/tailrec/StatementReplacer.java b/src/main/groovy/org/codehaus/groovy/transform/tailrec/StatementReplacer.java new file mode 100644 index 0000000..a3f1c61 --- /dev/null +++ b/src/main/groovy/org/codehaus/groovy/transform/tailrec/StatementReplacer.java @@ -0,0 +1,169 @@ +/* + * 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.lang.Closure; +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.CodeVisitorSupport; +import org.codehaus.groovy.ast.expr.ClosureExpression; +import org.codehaus.groovy.ast.stmt.BlockStatement; +import org.codehaus.groovy.ast.stmt.DoWhileStatement; +import org.codehaus.groovy.ast.stmt.ForStatement; +import org.codehaus.groovy.ast.stmt.IfStatement; +import org.codehaus.groovy.ast.stmt.Statement; +import org.codehaus.groovy.ast.stmt.WhileStatement; +import org.codehaus.groovy.runtime.DefaultGroovyMethods; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tool for replacing Statement objects in an AST by other Statement instances. + * <p> + * Within @TailRecursive it is used to swap ReturnStatements with looping back to RECUR label + */ +class StatementReplacer extends CodeVisitorSupport { + public void replaceIn(ASTNode root) { + root.visit(this); + } + + public void visitClosureExpression(ClosureExpression expression) { + closureLevel++; + try { + super.visitClosureExpression(expression); + } finally { + closureLevel--; + } + } + + public void visitBlockStatement(final BlockStatement block) { + List<Statement> copyOfStatements = new ArrayList<Statement>(block.getStatements()); + DefaultGroovyMethods.eachWithIndex(copyOfStatements, new Closure<Void>(this, this) { + public void doCall(Statement statement, final int index) { + replaceIfNecessary(statement, new Closure<Statement>(StatementReplacer.this, StatementReplacer.this) { + public Statement doCall(Statement node) { + block.getStatements().set(index, node); + return node; + } + }); + } + }); + super.visitBlockStatement(block); + } + + public void visitIfElse(final IfStatement ifElse) { + replaceIfNecessary(ifElse.getIfBlock(), new Closure<Statement>(this, this) { + public Statement doCall(Statement s) { + ifElse.setIfBlock(s); + return s; + } + }); + replaceIfNecessary(ifElse.getElseBlock(), new Closure<Statement>(this, this) { + public Statement doCall(Statement s) { + ifElse.setElseBlock(s); + return s; + } + }); + super.visitIfElse(ifElse); + } + + public void visitForLoop(final ForStatement forLoop) { + replaceIfNecessary(forLoop.getLoopBlock(), new Closure<Statement>(this, this) { + public Statement doCall(Statement s) { + forLoop.setLoopBlock(s); + return s; + } + }); + super.visitForLoop(forLoop); + } + + public void visitWhileLoop(final WhileStatement loop) { + replaceIfNecessary(loop.getLoopBlock(), new Closure<Statement>(this, this) { + public Statement doCall(Statement s) { + loop.setLoopBlock(s); + return s; + } + }); + super.visitWhileLoop(loop); + } + + public void visitDoWhileLoop(final DoWhileStatement loop) { + replaceIfNecessary(loop.getLoopBlock(), new Closure<Statement>(this, this) { + public Statement doCall(Statement s) { + loop.setLoopBlock(s); + return s; + } + }); + super.visitDoWhileLoop(loop); + } + + private void replaceIfNecessary(Statement nodeToCheck, Closure replacementCode) { + if (conditionFulfilled(nodeToCheck)) { + Statement replacement = replaceWith.call(nodeToCheck); + replacement.setSourcePosition(nodeToCheck); + replacement.copyNodeMetaData(nodeToCheck); + replacementCode.call(replacement); + } + } + + private boolean conditionFulfilled(ASTNode nodeToCheck) { + if (when.getMaximumNumberOfParameters() < 2) return when.call(nodeToCheck); + return when.call(nodeToCheck, isInClosure()); + } + + private boolean isInClosure() { + return closureLevel > 0; + } + + public Closure<Boolean> getWhen() { + return when; + } + + public void setWhen(Closure<Boolean> when) { + this.when = when; + } + + public Closure<Statement> getReplaceWith() { + return replaceWith; + } + + public void setReplaceWith(Closure<Statement> replaceWith) { + this.replaceWith = replaceWith; + } + + public int getClosureLevel() { + return closureLevel; + } + + public void setClosureLevel(int closureLevel) { + this.closureLevel = closureLevel; + } + + private Closure<Boolean> when = new Closure<Boolean>(this, this) { + public Boolean doCall(Statement node) { + return false; + } + }; + private Closure<Statement> replaceWith = new Closure<Statement>(this, this) { + public Statement doCall(Statement statement) { + return statement; + } + }; + private int closureLevel = 0; +} diff --git a/src/main/groovy/org/codehaus/groovy/transform/tailrec/VariableExpressionReplacer.groovy b/src/main/groovy/org/codehaus/groovy/transform/tailrec/VariableExpressionReplacer.groovy deleted file mode 100644 index 9dbb801..0000000 --- a/src/main/groovy/org/codehaus/groovy/transform/tailrec/VariableExpressionReplacer.groovy +++ /dev/null @@ -1,163 +0,0 @@ -/* - * 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.AutoFinal -import groovy.transform.CompileStatic -import org.codehaus.groovy.ast.ASTNode -import org.codehaus.groovy.ast.CodeVisitorSupport -import org.codehaus.groovy.ast.expr.BinaryExpression -import org.codehaus.groovy.ast.expr.BooleanExpression -import org.codehaus.groovy.ast.expr.Expression -import org.codehaus.groovy.ast.expr.ExpressionTransformer -import org.codehaus.groovy.ast.expr.VariableExpression -import org.codehaus.groovy.ast.stmt.AssertStatement -import org.codehaus.groovy.ast.stmt.CaseStatement -import org.codehaus.groovy.ast.stmt.DoWhileStatement -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.SwitchStatement -import org.codehaus.groovy.ast.stmt.SynchronizedStatement -import org.codehaus.groovy.ast.stmt.ThrowStatement -import org.codehaus.groovy.ast.stmt.WhileStatement -import org.codehaus.groovy.ast.tools.GeneralUtils - -import java.lang.reflect.Method - -/** - * Tool for replacing VariableExpression instances in an AST by other VariableExpression instances. - * Regardless of a real change taking place in nested expressions, all considered expression (trees) will be replaced. - * This could be optimized to accelerate compilation. - * - * Within @TailRecursive it is used - * - to swap the access of method args with the access to iteration variables - * - to swap the access of iteration variables with the access of temp vars - */ -@AutoFinal @CompileStatic -class VariableExpressionReplacer extends CodeVisitorSupport { - - Closure<Boolean> when = { VariableExpression node -> false } - Closure<VariableExpression> replaceWith = { VariableExpression variableExpression -> variableExpression } - - private ExpressionTransformer transformer - - synchronized void replaceIn(ASTNode root) { - transformer = new VariableExpressionTransformer(when: when, replaceWith: replaceWith) - root.visit(this) - } - - void visitReturnStatement(ReturnStatement statement) { - replaceExpressionPropertyWhenNecessary(statement) - super.visitReturnStatement(statement) - } - - void visitIfElse(IfStatement ifElse) { - replaceExpressionPropertyWhenNecessary(ifElse, 'booleanExpression', BooleanExpression) - super.visitIfElse(ifElse) - } - - void visitForLoop(ForStatement forLoop) { - replaceExpressionPropertyWhenNecessary(forLoop, 'collectionExpression') - super.visitForLoop(forLoop) - } - - /** - * It's the only Expression type in which replacing is considered. - * That's an abuse of the class, but I couldn't think of a better way. - */ - void visitBinaryExpression(BinaryExpression expression) { - //A hack: Only replace right expression b/c ReturnStatementToIterationConverter needs it that way :-/ - replaceExpressionPropertyWhenNecessary(expression, 'rightExpression') - expression.rightExpression.visit(this) - super.visitBinaryExpression(expression) - } - - void visitWhileLoop(WhileStatement loop) { - replaceExpressionPropertyWhenNecessary(loop, 'booleanExpression', BooleanExpression) - super.visitWhileLoop(loop) - } - - void visitDoWhileLoop(DoWhileStatement loop) { - replaceExpressionPropertyWhenNecessary(loop, 'booleanExpression', BooleanExpression) - super.visitDoWhileLoop(loop) - } - - void visitSwitch(SwitchStatement statement) { - replaceExpressionPropertyWhenNecessary(statement) - super.visitSwitch(statement) - } - - void visitCaseStatement(CaseStatement statement) { - replaceExpressionPropertyWhenNecessary(statement) - super.visitCaseStatement(statement) - } - - void visitExpressionStatement(ExpressionStatement statement) { - replaceExpressionPropertyWhenNecessary(statement) - super.visitExpressionStatement(statement) - } - - void visitThrowStatement(ThrowStatement statement) { - replaceExpressionPropertyWhenNecessary(statement) - super.visitThrowStatement(statement) - } - - void visitAssertStatement(AssertStatement statement) { - replaceExpressionPropertyWhenNecessary(statement, 'booleanExpression', BooleanExpression) - replaceExpressionPropertyWhenNecessary(statement, 'messageExpression') - super.visitAssertStatement(statement) - } - - void visitSynchronizedStatement(SynchronizedStatement statement) { - replaceExpressionPropertyWhenNecessary(statement) - super.visitSynchronizedStatement(statement) - } - - private void replaceExpressionPropertyWhenNecessary(ASTNode node, String propName = 'expression', Class propClass = Expression) { - Expression expr = getExpression(node, propName) - - if (expr instanceof VariableExpression) { - if (when(expr)) { - VariableExpression newExpr = replaceWith(expr) - replaceExpression(node, propName, propClass, expr, newExpr) - } - } else { - Expression newExpr = transformer.transform(expr) - replaceExpression(node, propName, propClass, expr, newExpr) - } - } - - private void replaceExpression(ASTNode node, String propName, Class propClass, Expression oldExpr, Expression newExpr) { - //Use reflection to enable CompileStatic - String setterName = GeneralUtils.getSetterName(propName) - Method setExpressionMethod = node.class.getMethod(setterName, propClass) - newExpr.copyNodeMetaData(oldExpr) - newExpr.setSourcePosition(oldExpr) - setExpressionMethod.invoke(node, newExpr) - } - - private Expression getExpression(ASTNode node, String propName) { - //Use reflection to enable CompileStatic - String getterName = GeneralUtils.getGetterName(propName) - Method getExpressionMethod = node.class.getMethod(getterName) - getExpressionMethod.invoke(node) as Expression - } -} diff --git a/src/main/groovy/org/codehaus/groovy/transform/tailrec/VariableExpressionReplacer.java b/src/main/groovy/org/codehaus/groovy/transform/tailrec/VariableExpressionReplacer.java new file mode 100644 index 0000000..e400983 --- /dev/null +++ b/src/main/groovy/org/codehaus/groovy/transform/tailrec/VariableExpressionReplacer.java @@ -0,0 +1,214 @@ +/* + * 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.lang.Closure; +import org.apache.groovy.internal.util.UncheckedThrow; +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.CodeVisitorSupport; +import org.codehaus.groovy.ast.expr.BinaryExpression; +import org.codehaus.groovy.ast.expr.BooleanExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.ExpressionTransformer; +import org.codehaus.groovy.ast.expr.VariableExpression; +import org.codehaus.groovy.ast.stmt.AssertStatement; +import org.codehaus.groovy.ast.stmt.CaseStatement; +import org.codehaus.groovy.ast.stmt.DoWhileStatement; +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.SwitchStatement; +import org.codehaus.groovy.ast.stmt.SynchronizedStatement; +import org.codehaus.groovy.ast.stmt.ThrowStatement; +import org.codehaus.groovy.ast.stmt.WhileStatement; +import org.codehaus.groovy.ast.tools.GeneralUtils; +import org.codehaus.groovy.runtime.DefaultGroovyMethods; + +import java.lang.reflect.Method; + +/** + * Tool for replacing VariableExpression instances in an AST by other VariableExpression instances. + * Regardless of a real change taking place in nested expressions, all considered expression (trees) will be replaced. + * This could be optimized to accelerate compilation. + * <p> + * Within @TailRecursive it is used + * - to swap the access of method args with the access to iteration variables + * - to swap the access of iteration variables with the access of temp vars + */ +class VariableExpressionReplacer extends CodeVisitorSupport { + @Override + public void visitReturnStatement(final ReturnStatement statement) { + replaceExpressionPropertyWhenNecessary(statement); + super.visitReturnStatement(statement); + } + + @Override + public void visitIfElse(final IfStatement ifElse) { + replaceExpressionPropertyWhenNecessary(ifElse, "booleanExpression", BooleanExpression.class); + super.visitIfElse(ifElse); + } + + @Override + public void visitForLoop(final ForStatement forLoop) { + replaceExpressionPropertyWhenNecessary(forLoop, "collectionExpression"); + super.visitForLoop(forLoop); + } + + /** + * It's the only Expression type in which replacing is considered. + * That's an abuse of the class, but I couldn't think of a better way. + */ + @Override + public void visitBinaryExpression(final BinaryExpression expression) { + //A hack: Only replace right expression b/c ReturnStatementToIterationConverter needs it that way :-/ + replaceExpressionPropertyWhenNecessary(expression, "rightExpression"); + expression.getRightExpression().visit(this); + super.visitBinaryExpression(expression); + } + + @Override + public void visitWhileLoop(final WhileStatement loop) { + replaceExpressionPropertyWhenNecessary(loop, "booleanExpression", BooleanExpression.class); + super.visitWhileLoop(loop); + } + + @Override + public void visitDoWhileLoop(final DoWhileStatement loop) { + replaceExpressionPropertyWhenNecessary(loop, "booleanExpression", BooleanExpression.class); + super.visitDoWhileLoop(loop); + } + + @Override + public void visitSwitch(final SwitchStatement statement) { + replaceExpressionPropertyWhenNecessary(statement); + super.visitSwitch(statement); + } + + @Override + public void visitCaseStatement(final CaseStatement statement) { + replaceExpressionPropertyWhenNecessary(statement); + super.visitCaseStatement(statement); + } + + @Override + public void visitExpressionStatement(final ExpressionStatement statement) { + replaceExpressionPropertyWhenNecessary(statement); + super.visitExpressionStatement(statement); + } + + @Override + public void visitThrowStatement(final ThrowStatement statement) { + replaceExpressionPropertyWhenNecessary(statement); + super.visitThrowStatement(statement); + } + + @Override + public void visitAssertStatement(final AssertStatement statement) { + replaceExpressionPropertyWhenNecessary(statement, "booleanExpression", BooleanExpression.class); + replaceExpressionPropertyWhenNecessary(statement, "messageExpression"); + super.visitAssertStatement(statement); + } + + @Override + public void visitSynchronizedStatement(final SynchronizedStatement statement) { + replaceExpressionPropertyWhenNecessary(statement); + super.visitSynchronizedStatement(statement); + } + + public synchronized void replaceIn(final ASTNode root) { + transformer = new VariableExpressionTransformer(when, replaceWith); + root.visit(this); + } + + private void replaceExpressionPropertyWhenNecessary(final ASTNode node, final String propName, final Class propClass) { + Expression expr = getExpression(node, propName); + + if (expr instanceof VariableExpression) { + if (when.call(expr)) { + VariableExpression newExpr = replaceWith.call(expr); + replaceExpression(node, propName, propClass, expr, newExpr); + } + } else { + Expression newExpr = transformer.transform(expr); + replaceExpression(node, propName, propClass, expr, newExpr); + } + } + + private void replaceExpressionPropertyWhenNecessary(final ASTNode node, final String propName) { + replaceExpressionPropertyWhenNecessary(node, propName, Expression.class); + } + + private void replaceExpressionPropertyWhenNecessary(final ASTNode node) { + replaceExpressionPropertyWhenNecessary(node, "expression", Expression.class); + } + + private void replaceExpression(final ASTNode node, final String propName, final Class propClass, final Expression oldExpr, final Expression newExpr) { + try { + //Use reflection to enable CompileStatic + String setterName = GeneralUtils.getSetterName(propName); + Method setExpressionMethod = node.getClass().getMethod(setterName, propClass); + newExpr.copyNodeMetaData(oldExpr); + newExpr.setSourcePosition(oldExpr); + setExpressionMethod.invoke(node, newExpr); + } catch (Throwable t) { + UncheckedThrow.rethrow(t); + } + } + + private Expression getExpression(final ASTNode node, final String propName) { + try { + //Use reflection to enable CompileStatic + String getterName = GeneralUtils.getGetterName(propName); + Method getExpressionMethod = node.getClass().getMethod(getterName); + return DefaultGroovyMethods.asType(getExpressionMethod.invoke(node), Expression.class); + } catch (Throwable t) { + UncheckedThrow.rethrow(t); + return null; + } + } + + public Closure<Boolean> getWhen() { + return when; + } + + public void setWhen(Closure<Boolean> when) { + this.when = when; + } + + public Closure<VariableExpression> getReplaceWith() { + return replaceWith; + } + + public void setReplaceWith(Closure<VariableExpression> replaceWith) { + this.replaceWith = replaceWith; + } + + private Closure<Boolean> when = new Closure<Boolean>(this, this) { + public Boolean doCall(final VariableExpression node) { + return false; + } + }; + private Closure<VariableExpression> replaceWith = new Closure<VariableExpression>(this, this) { + public VariableExpression doCall(final VariableExpression variableExpression) { + return variableExpression; + } + }; + private ExpressionTransformer transformer; +} diff --git a/src/main/groovy/org/codehaus/groovy/transform/tailrec/VariableExpressionTransformer.groovy b/src/main/groovy/org/codehaus/groovy/transform/tailrec/VariableExpressionTransformer.groovy deleted file mode 100644 index 9683bc9..0000000 --- a/src/main/groovy/org/codehaus/groovy/transform/tailrec/VariableExpressionTransformer.groovy +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.expr.Expression -import org.codehaus.groovy.ast.expr.ExpressionTransformer -import org.codehaus.groovy.ast.expr.VariableExpression - -/** - * An expression transformer used in the process of replacing the access to variables - */ -@CompileStatic -class VariableExpressionTransformer implements ExpressionTransformer { - - Closure<Boolean> when - Closure<VariableExpression> replaceWith - - @Override - @SuppressWarnings('Instanceof') - Expression transform(Expression expr) { - if ((expr instanceof VariableExpression) && when(expr)) { - VariableExpression newExpr = replaceWith(expr) - newExpr.sourcePosition = expr - newExpr.copyNodeMetaData(expr) - return newExpr - } - expr.transformExpression(this) - } -} diff --git a/src/main/groovy/org/codehaus/groovy/transform/tailrec/VariableExpressionTransformer.java b/src/main/groovy/org/codehaus/groovy/transform/tailrec/VariableExpressionTransformer.java new file mode 100644 index 0000000..78a8360 --- /dev/null +++ b/src/main/groovy/org/codehaus/groovy/transform/tailrec/VariableExpressionTransformer.java @@ -0,0 +1,66 @@ +/* + * 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.lang.Closure; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.ExpressionTransformer; +import org.codehaus.groovy.ast.expr.VariableExpression; + +/** + * An expression transformer used in the process of replacing the access to variables + */ +class VariableExpressionTransformer implements ExpressionTransformer { + public VariableExpressionTransformer(Closure<Boolean> when, Closure<VariableExpression> replaceWith) { + this.when = when; + this.replaceWith = replaceWith; + } + + @Override + @SuppressWarnings("Instanceof") + public Expression transform(Expression expr) { + if ((expr instanceof VariableExpression) && when.call(expr)) { + VariableExpression newExpr = replaceWith.call(expr); + newExpr.setSourcePosition(expr); + newExpr.copyNodeMetaData(expr); + return newExpr; + } + + return expr.transformExpression(this); + } + + public Closure<Boolean> getWhen() { + return when; + } + + public void setWhen(Closure<Boolean> when) { + this.when = when; + } + + public Closure<VariableExpression> getReplaceWith() { + return replaceWith; + } + + public void setReplaceWith(Closure<VariableExpression> replaceWith) { + this.replaceWith = replaceWith; + } + + private Closure<Boolean> when; + private Closure<VariableExpression> replaceWith; +}
