From 51e8ff721cc7079212119ba44fcc4bf0f9818d1e Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Sun, 6 Dec 2015 11:20:51 +0000 Subject: Replace captured variable references in method bodies --- .../org/zwobble/couscous/ast/AssignmentNode.java | 9 ++++ .../zwobble/couscous/ast/ConstructorCallNode.java | 8 ++++ .../org/zwobble/couscous/ast/ExpressionNode.java | 4 ++ .../couscous/ast/ExpressionStatementNode.java | 7 +++ .../org/zwobble/couscous/ast/FieldAccessNode.java | 7 +++ .../org/zwobble/couscous/ast/IfStatementNode.java | 11 +++++ .../java/org/zwobble/couscous/ast/LiteralNode.java | 7 +++ .../couscous/ast/LocalVariableDeclarationNode.java | 7 +++ .../org/zwobble/couscous/ast/MethodCallNode.java | 12 ++++- .../java/org/zwobble/couscous/ast/ReturnNode.java | 7 +++ .../org/zwobble/couscous/ast/StatementNode.java | 4 ++ .../zwobble/couscous/ast/StaticMethodCallNode.java | 11 +++++ .../couscous/ast/TernaryConditionalNode.java | 10 +++++ .../zwobble/couscous/ast/ThisReferenceNode.java | 7 +++ .../couscous/ast/VariableReferenceNode.java | 9 +++- .../java/org/zwobble/couscous/ast/WhileNode.java | 10 +++++ .../couscous/frontends/java/JavaFrontend.java | 4 +- .../couscous/frontends/java/JavaReader.java | 51 ++++++++++++++-------- 18 files changed, 164 insertions(+), 21 deletions(-) (limited to 'src/main/java') diff --git a/src/main/java/org/zwobble/couscous/ast/AssignmentNode.java b/src/main/java/org/zwobble/couscous/ast/AssignmentNode.java index b6c8f9c..15ecf8f 100644 --- a/src/main/java/org/zwobble/couscous/ast/AssignmentNode.java +++ b/src/main/java/org/zwobble/couscous/ast/AssignmentNode.java @@ -2,6 +2,8 @@ package org.zwobble.couscous.ast; import org.zwobble.couscous.ast.visitors.ExpressionNodeMapper; +import java.util.function.Function; + import static org.zwobble.couscous.ast.ExpressionStatementNode.expressionStatement; import static org.zwobble.couscous.ast.VariableReferenceNode.reference; @@ -43,6 +45,13 @@ public class AssignmentNode implements ExpressionNode { return visitor.visit(this); } + @Override + public ExpressionNode replaceExpressions(Function replace) { + return new AssignmentNode( + (AssignableExpressionNode) replace.apply(target), + replace.apply(value)); + } + @Override public TypeName getType() { return value.getType(); diff --git a/src/main/java/org/zwobble/couscous/ast/ConstructorCallNode.java b/src/main/java/org/zwobble/couscous/ast/ConstructorCallNode.java index 6a4655a..ab4b1ea 100644 --- a/src/main/java/org/zwobble/couscous/ast/ConstructorCallNode.java +++ b/src/main/java/org/zwobble/couscous/ast/ConstructorCallNode.java @@ -1,9 +1,12 @@ package org.zwobble.couscous.ast; import java.util.List; +import java.util.function.Function; import org.zwobble.couscous.ast.visitors.ExpressionNodeMapper; +import static org.zwobble.couscous.util.ExtraLists.eagerMap; + public class ConstructorCallNode implements ExpressionNode { public static ConstructorCallNode constructorCall( TypeName type, @@ -35,6 +38,11 @@ public class ConstructorCallNode implements ExpressionNode { return visitor.visit(this); } + @Override + public ExpressionNode replaceExpressions(Function replace) { + return new ConstructorCallNode(type, eagerMap(arguments, replace::apply)); + } + @Override public String toString() { return "ConstructorCallNode(type=" + type + ", arguments=" + arguments diff --git a/src/main/java/org/zwobble/couscous/ast/ExpressionNode.java b/src/main/java/org/zwobble/couscous/ast/ExpressionNode.java index 65757ad..0f1ab4f 100644 --- a/src/main/java/org/zwobble/couscous/ast/ExpressionNode.java +++ b/src/main/java/org/zwobble/couscous/ast/ExpressionNode.java @@ -3,6 +3,8 @@ package org.zwobble.couscous.ast; import org.zwobble.couscous.ast.visitors.ExpressionNodeMapper; import org.zwobble.couscous.ast.visitors.NodeMapper; +import java.util.function.Function; + public interface ExpressionNode extends Node { TypeName getType(); T accept(ExpressionNodeMapper visitor); @@ -56,4 +58,6 @@ public interface ExpressionNode extends Node { } }); } + + ExpressionNode replaceExpressions(Function replace); } diff --git a/src/main/java/org/zwobble/couscous/ast/ExpressionStatementNode.java b/src/main/java/org/zwobble/couscous/ast/ExpressionStatementNode.java index b814076..0ac7381 100644 --- a/src/main/java/org/zwobble/couscous/ast/ExpressionStatementNode.java +++ b/src/main/java/org/zwobble/couscous/ast/ExpressionStatementNode.java @@ -2,6 +2,8 @@ package org.zwobble.couscous.ast; import org.zwobble.couscous.ast.visitors.StatementNodeMapper; +import java.util.function.Function; + public class ExpressionStatementNode implements StatementNode { public static ExpressionStatementNode expressionStatement(ExpressionNode expression) { return new ExpressionStatementNode(expression); @@ -22,6 +24,11 @@ public class ExpressionStatementNode implements StatementNode { return visitor.visit(this); } + @Override + public StatementNode replaceExpressions(Function replace) { + return new ExpressionStatementNode(replace.apply(expression)); + } + @Override public String toString() { return "ExpressionStatementNode(expression=" + expression + ")"; diff --git a/src/main/java/org/zwobble/couscous/ast/FieldAccessNode.java b/src/main/java/org/zwobble/couscous/ast/FieldAccessNode.java index f3c92f9..9f881b4 100644 --- a/src/main/java/org/zwobble/couscous/ast/FieldAccessNode.java +++ b/src/main/java/org/zwobble/couscous/ast/FieldAccessNode.java @@ -3,6 +3,8 @@ package org.zwobble.couscous.ast; import org.zwobble.couscous.ast.visitors.AssignableExpressionNodeVisitor; import org.zwobble.couscous.ast.visitors.ExpressionNodeMapper; +import java.util.function.Function; + public class FieldAccessNode implements AssignableExpressionNode { public static FieldAccessNode fieldAccess( ExpressionNode left, @@ -41,6 +43,11 @@ public class FieldAccessNode implements AssignableExpressionNode { return visitor.visit(this); } + @Override + public ExpressionNode replaceExpressions(Function replace) { + return new FieldAccessNode(replace.apply(left), fieldName, type); + } + @Override public void accept(AssignableExpressionNodeVisitor mapper) { mapper.visit(this); diff --git a/src/main/java/org/zwobble/couscous/ast/IfStatementNode.java b/src/main/java/org/zwobble/couscous/ast/IfStatementNode.java index ae3e29b..cb5eee8 100644 --- a/src/main/java/org/zwobble/couscous/ast/IfStatementNode.java +++ b/src/main/java/org/zwobble/couscous/ast/IfStatementNode.java @@ -1,9 +1,12 @@ package org.zwobble.couscous.ast; import java.util.List; +import java.util.function.Function; import org.zwobble.couscous.ast.visitors.StatementNodeMapper; +import static org.zwobble.couscous.util.ExtraLists.eagerMap; + public class IfStatementNode implements StatementNode { public static IfStatementNode ifStatement( ExpressionNode condition, @@ -42,6 +45,14 @@ public class IfStatementNode implements StatementNode { return visitor.visit(this); } + @Override + public StatementNode replaceExpressions(Function replace) { + return new IfStatementNode( + replace.apply(condition), + eagerMap(trueBranch, statement -> statement.replaceExpressions(replace)), + eagerMap(falseBranch, statement -> statement.replaceExpressions(replace))); + } + @Override public String toString() { return "IfStatementNode(condition=" + condition + ", trueBranch=" diff --git a/src/main/java/org/zwobble/couscous/ast/LiteralNode.java b/src/main/java/org/zwobble/couscous/ast/LiteralNode.java index ca005de..1aa3293 100644 --- a/src/main/java/org/zwobble/couscous/ast/LiteralNode.java +++ b/src/main/java/org/zwobble/couscous/ast/LiteralNode.java @@ -6,6 +6,8 @@ import org.zwobble.couscous.values.IntegerValue; import org.zwobble.couscous.values.PrimitiveValue; import org.zwobble.couscous.values.StringValue; +import java.util.function.Function; + import static org.zwobble.couscous.values.PrimitiveValues.value; public class LiteralNode implements ExpressionNode { @@ -50,6 +52,11 @@ public class LiteralNode implements ExpressionNode { return visitor.visit(this); } + @Override + public ExpressionNode replaceExpressions(Function replace) { + return this; + } + @Override public String toString() { return "LiteralNode(value=" + value + ", type=" + type + ")"; diff --git a/src/main/java/org/zwobble/couscous/ast/LocalVariableDeclarationNode.java b/src/main/java/org/zwobble/couscous/ast/LocalVariableDeclarationNode.java index bb272fc..3499e26 100644 --- a/src/main/java/org/zwobble/couscous/ast/LocalVariableDeclarationNode.java +++ b/src/main/java/org/zwobble/couscous/ast/LocalVariableDeclarationNode.java @@ -2,6 +2,8 @@ package org.zwobble.couscous.ast; import org.zwobble.couscous.ast.visitors.StatementNodeMapper; +import java.util.function.Function; + import static org.zwobble.couscous.ast.VariableDeclaration.var; public class LocalVariableDeclarationNode implements VariableNode, StatementNode { @@ -50,6 +52,11 @@ public class LocalVariableDeclarationNode implements VariableNode, StatementNode return visitor.visit(this); } + @Override + public StatementNode replaceExpressions(Function replace) { + return new LocalVariableDeclarationNode(declaration, replace.apply(initialValue)); + } + @Override public String toString() { return "LocalVariableDeclarationNode(declaration=" + declaration diff --git a/src/main/java/org/zwobble/couscous/ast/MethodCallNode.java b/src/main/java/org/zwobble/couscous/ast/MethodCallNode.java index cce140f..f65a9ae 100644 --- a/src/main/java/org/zwobble/couscous/ast/MethodCallNode.java +++ b/src/main/java/org/zwobble/couscous/ast/MethodCallNode.java @@ -1,14 +1,15 @@ package org.zwobble.couscous.ast; -import com.sun.org.apache.xpath.internal.operations.Bool; import org.zwobble.couscous.ast.visitors.ExpressionNodeMapper; import org.zwobble.couscous.values.BooleanValue; import org.zwobble.couscous.values.IntegerValue; import java.util.Collections; import java.util.List; +import java.util.function.Function; import static java.util.Arrays.asList; +import static org.zwobble.couscous.util.ExtraLists.eagerMap; public class MethodCallNode implements ExpressionNode { public static ExpressionNode not(ExpressionNode value) { @@ -116,6 +117,15 @@ public class MethodCallNode implements ExpressionNode { return visitor.visit(this); } + @Override + public ExpressionNode replaceExpressions(Function replace) { + return new MethodCallNode( + replace.apply(receiver), + methodName, + eagerMap(arguments, replace::apply), + type); + } + @Override public String toString() { return "MethodCallNode(receiver=" + receiver + ", methodName=" diff --git a/src/main/java/org/zwobble/couscous/ast/ReturnNode.java b/src/main/java/org/zwobble/couscous/ast/ReturnNode.java index 008b985..68330af 100644 --- a/src/main/java/org/zwobble/couscous/ast/ReturnNode.java +++ b/src/main/java/org/zwobble/couscous/ast/ReturnNode.java @@ -2,6 +2,8 @@ package org.zwobble.couscous.ast; import org.zwobble.couscous.ast.visitors.StatementNodeMapper; +import java.util.function.Function; + public class ReturnNode implements StatementNode { public static ReturnNode returns(ExpressionNode value) { return new ReturnNode(value); @@ -22,6 +24,11 @@ public class ReturnNode implements StatementNode { return visitor.visit(this); } + @Override + public StatementNode replaceExpressions(Function replace) { + return new ReturnNode(replace.apply(value)); + } + @Override public String toString() { return "ReturnNode(value=" + value + ")"; diff --git a/src/main/java/org/zwobble/couscous/ast/StatementNode.java b/src/main/java/org/zwobble/couscous/ast/StatementNode.java index a052917..4b9c9de 100644 --- a/src/main/java/org/zwobble/couscous/ast/StatementNode.java +++ b/src/main/java/org/zwobble/couscous/ast/StatementNode.java @@ -3,6 +3,8 @@ package org.zwobble.couscous.ast; import org.zwobble.couscous.ast.visitors.NodeMapper; import org.zwobble.couscous.ast.visitors.StatementNodeMapper; +import java.util.function.Function; + public interface StatementNode extends Node { T accept(StatementNodeMapper visitor); @@ -35,4 +37,6 @@ public interface StatementNode extends Node { } }); } + + StatementNode replaceExpressions(Function replace); } diff --git a/src/main/java/org/zwobble/couscous/ast/StaticMethodCallNode.java b/src/main/java/org/zwobble/couscous/ast/StaticMethodCallNode.java index b33e94c..ccb9854 100644 --- a/src/main/java/org/zwobble/couscous/ast/StaticMethodCallNode.java +++ b/src/main/java/org/zwobble/couscous/ast/StaticMethodCallNode.java @@ -1,6 +1,7 @@ package org.zwobble.couscous.ast; import java.util.List; +import java.util.function.Function; import org.zwobble.couscous.ast.visitors.ExpressionNodeMapper; import org.zwobble.couscous.values.BooleanValue; @@ -9,6 +10,7 @@ import org.zwobble.couscous.values.InternalCouscousValue; import org.zwobble.couscous.values.ObjectValues; import static java.util.Arrays.asList; +import static org.zwobble.couscous.util.ExtraLists.eagerMap; public class StaticMethodCallNode implements ExpressionNode { public static ExpressionNode same(ExpressionNode left, ExpressionNode right) { @@ -100,6 +102,15 @@ public class StaticMethodCallNode implements ExpressionNode { return visitor.visit(this); } + @Override + public ExpressionNode replaceExpressions(Function replace) { + return new StaticMethodCallNode( + className, + methodName, + eagerMap(arguments, replace::apply), + type); + } + @Override public String toString() { return "StaticMethodCallNode(className=" + className + ", methodName=" diff --git a/src/main/java/org/zwobble/couscous/ast/TernaryConditionalNode.java b/src/main/java/org/zwobble/couscous/ast/TernaryConditionalNode.java index e9d3f6c..f370b0a 100644 --- a/src/main/java/org/zwobble/couscous/ast/TernaryConditionalNode.java +++ b/src/main/java/org/zwobble/couscous/ast/TernaryConditionalNode.java @@ -2,6 +2,8 @@ package org.zwobble.couscous.ast; import org.zwobble.couscous.ast.visitors.ExpressionNodeMapper; +import java.util.function.Function; + public class TernaryConditionalNode implements ExpressionNode { public static TernaryConditionalNode ternaryConditional( ExpressionNode condition, @@ -40,6 +42,14 @@ public class TernaryConditionalNode implements ExpressionNode { return visitor.visit(this); } + @Override + public ExpressionNode replaceExpressions(Function replace) { + return new TernaryConditionalNode( + replace.apply(condition), + replace.apply(ifTrue), + replace.apply(ifFalse)); + } + @Override public TypeName getType() { return ifTrue.getType(); diff --git a/src/main/java/org/zwobble/couscous/ast/ThisReferenceNode.java b/src/main/java/org/zwobble/couscous/ast/ThisReferenceNode.java index 4e7adc6..3ef5527 100644 --- a/src/main/java/org/zwobble/couscous/ast/ThisReferenceNode.java +++ b/src/main/java/org/zwobble/couscous/ast/ThisReferenceNode.java @@ -2,6 +2,8 @@ package org.zwobble.couscous.ast; import org.zwobble.couscous.ast.visitors.ExpressionNodeMapper; +import java.util.function.Function; + public class ThisReferenceNode implements ExpressionNode { public static ThisReferenceNode thisReference(TypeName type) { return new ThisReferenceNode(type); @@ -22,6 +24,11 @@ public class ThisReferenceNode implements ExpressionNode { return visitor.visit(this); } + @Override + public ExpressionNode replaceExpressions(Function replace) { + return this; + } + @Override public String toString() { return "ThisReferenceNode(type=" + type + ")"; diff --git a/src/main/java/org/zwobble/couscous/ast/VariableReferenceNode.java b/src/main/java/org/zwobble/couscous/ast/VariableReferenceNode.java index e95510f..b576389 100644 --- a/src/main/java/org/zwobble/couscous/ast/VariableReferenceNode.java +++ b/src/main/java/org/zwobble/couscous/ast/VariableReferenceNode.java @@ -3,6 +3,8 @@ package org.zwobble.couscous.ast; import org.zwobble.couscous.ast.visitors.AssignableExpressionNodeVisitor; import org.zwobble.couscous.ast.visitors.ExpressionNodeMapper; +import java.util.function.Function; + public class VariableReferenceNode implements AssignableExpressionNode { public static VariableReferenceNode reference(VariableNode variable) { return new VariableReferenceNode(variable.getDeclaration()); @@ -26,7 +28,12 @@ public class VariableReferenceNode implements AssignableExpressionNode { public T accept(ExpressionNodeMapper visitor) { return visitor.visit(this); } - + + @Override + public ExpressionNode replaceExpressions(Function replace) { + return this; + } + public String getReferentId() { return referent.getId(); } diff --git a/src/main/java/org/zwobble/couscous/ast/WhileNode.java b/src/main/java/org/zwobble/couscous/ast/WhileNode.java index 0a67aef..6462821 100644 --- a/src/main/java/org/zwobble/couscous/ast/WhileNode.java +++ b/src/main/java/org/zwobble/couscous/ast/WhileNode.java @@ -4,6 +4,9 @@ import com.google.common.base.Objects; import org.zwobble.couscous.ast.visitors.StatementNodeMapper; import java.util.List; +import java.util.function.Function; + +import static org.zwobble.couscous.util.ExtraLists.eagerMap; public class WhileNode implements StatementNode { public static WhileNode whileLoop(ExpressionNode condition, List body) { @@ -31,6 +34,13 @@ public class WhileNode implements StatementNode { return visitor.visit(this); } + @Override + public StatementNode replaceExpressions(Function replace) { + return new WhileNode( + replace.apply(condition), + eagerMap(body, statement -> statement.replaceExpressions(replace))); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/org/zwobble/couscous/frontends/java/JavaFrontend.java b/src/main/java/org/zwobble/couscous/frontends/java/JavaFrontend.java index fa44676..2e598d5 100644 --- a/src/main/java/org/zwobble/couscous/frontends/java/JavaFrontend.java +++ b/src/main/java/org/zwobble/couscous/frontends/java/JavaFrontend.java @@ -37,11 +37,11 @@ public class JavaFrontend implements Frontend { private void ensureDeclarationsAreUnique(List classNodes) { List declarations = eagerFlatMap(classNodes, this::findDeclarations); - Multimaps.index(declarations, declaration -> declaration.getId()) + Multimaps.index(declarations, VariableDeclaration::getId) .asMap() .forEach((id, declarationsWithId) -> { if (declarationsWithId.size() > 1) { - throw new RuntimeException("Declaration " + id + " is declared more than once"); + throw new RuntimeException("Declaration " + id + " is declared " + declarationsWithId.size() + " times"); } }); } diff --git a/src/main/java/org/zwobble/couscous/frontends/java/JavaReader.java b/src/main/java/org/zwobble/couscous/frontends/java/JavaReader.java index 92d9fcd..a9531e6 100644 --- a/src/main/java/org/zwobble/couscous/frontends/java/JavaReader.java +++ b/src/main/java/org/zwobble/couscous/frontends/java/JavaReader.java @@ -1,6 +1,7 @@ package org.zwobble.couscous.frontends.java; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; import org.eclipse.jdt.core.dom.*; import org.zwobble.couscous.ast.*; import org.zwobble.couscous.ast.VariableDeclaration; @@ -9,19 +10,21 @@ import java.io.IOException; import java.nio.file.Path; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.function.Function; import static java.util.Arrays.asList; import static org.zwobble.couscous.ast.AnnotationNode.annotation; import static org.zwobble.couscous.ast.AssignmentNode.assignStatement; import static org.zwobble.couscous.ast.ConstructorNode.constructor; import static org.zwobble.couscous.ast.FieldAccessNode.fieldAccess; -import static org.zwobble.couscous.ast.LocalVariableDeclarationNode.localVariableDeclaration; import static org.zwobble.couscous.ast.ReturnNode.returns; import static org.zwobble.couscous.ast.ThisReferenceNode.thisReference; import static org.zwobble.couscous.ast.VariableReferenceNode.reference; import static org.zwobble.couscous.frontends.java.FreeVariables.findFreeVariables; import static org.zwobble.couscous.frontends.java.JavaTypes.typeOf; +import static org.zwobble.couscous.util.Casts.tryCast; import static org.zwobble.couscous.util.ExtraLists.*; public class JavaReader { @@ -56,14 +59,12 @@ public class JavaReader { List freeVariables = findFreeVariables( concat(lambda.getFormalArguments(), lambda.getBody())); - List restoreCaptures = generateCaptureRestoration(className, freeVariables); - MethodNode method = MethodNode.method( Collections.emptyList(), false, functionalInterfaceMethod.getName(), lambda.getFormalArguments(), - concat(restoreCaptures, lambda.getBody())); + replaceCaptureReferences(className, lambda.getBody(), freeVariables)); ClassNode classNode = new ClassNodeBuilder(className) .constructor(buildConstructor(className, freeVariables)) @@ -74,17 +75,34 @@ public class JavaReader { return new GeneratedClosure(classNode.getName(), freeVariables); } - private List generateCaptureRestoration(TypeName className, List freeVariables) { - // TODO: rewrite the AST to reference field directly rather than assigning - // This is especially bad since we use the same declaration in three places - // (the original declaration, the captured field, and the local to alias the field) - return eagerMap(freeVariables, freeVariable -> - localVariableDeclaration( - freeVariable, - fieldAccess( - thisReference(className), - freeVariable.getName(), - freeVariable.getType()))); + private List replaceCaptureReferences( + TypeName className, + List body, + List freeVariables) + { + Map freeVariablesById = Maps.transformValues( + Maps.uniqueIndex(freeVariables, VariableDeclaration::getId), + freeVariable -> fieldAccess( + thisReference(className), + freeVariable.getName(), + freeVariable.getType())); + Function replaceExpression = new CaptureReplacer(freeVariablesById); + return eagerMap(body, statement -> statement.replaceExpressions(replaceExpression)); + } + + private class CaptureReplacer implements Function { + private final Map freeVariablesById; + + public CaptureReplacer(Map freeVariablesById) { + this.freeVariablesById = freeVariablesById; + } + + @Override + public ExpressionNode apply(ExpressionNode expression) { + return tryCast(VariableReferenceNode.class, expression) + .flatMap(variableNode -> Optional.ofNullable(freeVariablesById.get(variableNode.getReferentId()))) + .orElseGet(() -> expression.replaceExpressions(this)); + } } private ConstructorNode buildConstructor(TypeName type, List freeVariables) { @@ -104,10 +122,9 @@ public class JavaReader { TypeName className = generateClassName(declaration); TypeDeclarationBody bodyDeclarations = readTypeDeclarationBody(declaration.bodyDeclarations()); List freeVariables = findFreeVariables(bodyDeclarations.getNodes()); - List restoreCaptures = generateCaptureRestoration(className, freeVariables); List methods = eagerMap(bodyDeclarations.getMethods(), method -> - method.mapBody(body -> concat(restoreCaptures, body))); + method.mapBody(body -> replaceCaptureReferences(className, body, freeVariables))); ClassNode classNode = ClassNode.declareClass( className, -- cgit v1.2.3