From bb0b36b86f841ec6340fe6e727ed2ea9db1c2d64 Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Mon, 4 Jan 2016 18:58:19 +0000 Subject: Capture "this" references --- .../zwobble/couscous/ast/ConstructorCallNode.java | 8 +-- .../org/zwobble/couscous/ast/ReferenceNode.java | 10 +++ .../zwobble/couscous/ast/ThisReferenceNode.java | 7 +- .../couscous/ast/VariableReferenceNode.java | 7 +- .../couscous/ast/structure/NodeStructure.java | 2 +- .../backends/python/PythonCodeGenerator.java | 2 +- .../couscous/frontends/java/FreeVariables.java | 28 ++++---- .../couscous/frontends/java/GeneratedClosure.java | 7 +- .../frontends/java/JavaExpressionReader.java | 5 +- .../couscous/frontends/java/JavaReader.java | 82 ++++++++++++++-------- .../zwobble/couscous/interpreter/Evaluator.java | 2 +- .../org/zwobble/couscous/tests/CompilerTests.java | 3 - .../tests/frontends/java/FreeVariablesTests.java | 9 +-- 13 files changed, 108 insertions(+), 64 deletions(-) create mode 100644 src/main/java/org/zwobble/couscous/ast/ReferenceNode.java (limited to 'src') diff --git a/src/main/java/org/zwobble/couscous/ast/ConstructorCallNode.java b/src/main/java/org/zwobble/couscous/ast/ConstructorCallNode.java index ab4b1ea..cad359d 100644 --- a/src/main/java/org/zwobble/couscous/ast/ConstructorCallNode.java +++ b/src/main/java/org/zwobble/couscous/ast/ConstructorCallNode.java @@ -10,16 +10,16 @@ import static org.zwobble.couscous.util.ExtraLists.eagerMap; public class ConstructorCallNode implements ExpressionNode { public static ConstructorCallNode constructorCall( TypeName type, - List arguments) { + List arguments) { return new ConstructorCallNode(type, arguments); } private final TypeName type; - private final List arguments; + private final List arguments; public ConstructorCallNode( TypeName type, - List arguments) { + List arguments) { this.type = type; this.arguments = arguments; } @@ -29,7 +29,7 @@ public class ConstructorCallNode implements ExpressionNode { return type; } - public List getArguments() { + public List getArguments() { return arguments; } diff --git a/src/main/java/org/zwobble/couscous/ast/ReferenceNode.java b/src/main/java/org/zwobble/couscous/ast/ReferenceNode.java new file mode 100644 index 0000000..6e76fcd --- /dev/null +++ b/src/main/java/org/zwobble/couscous/ast/ReferenceNode.java @@ -0,0 +1,10 @@ +package org.zwobble.couscous.ast; + +public interface ReferenceNode extends ExpressionNode { + interface Visitor { + T visit(VariableReferenceNode variableReference); + T visit(ThisReferenceNode thisReference); + } + + T accept(Visitor visitor); +} diff --git a/src/main/java/org/zwobble/couscous/ast/ThisReferenceNode.java b/src/main/java/org/zwobble/couscous/ast/ThisReferenceNode.java index 3ef5527..a4851c8 100644 --- a/src/main/java/org/zwobble/couscous/ast/ThisReferenceNode.java +++ b/src/main/java/org/zwobble/couscous/ast/ThisReferenceNode.java @@ -4,7 +4,7 @@ import org.zwobble.couscous.ast.visitors.ExpressionNodeMapper; import java.util.function.Function; -public class ThisReferenceNode implements ExpressionNode { +public class ThisReferenceNode implements ExpressionNode, ReferenceNode { public static ThisReferenceNode thisReference(TypeName type) { return new ThisReferenceNode(type); } @@ -29,6 +29,11 @@ public class ThisReferenceNode implements ExpressionNode { return this; } + @Override + public T accept(Visitor visitor) { + return visitor.visit(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 50462e9..c1b022a 100644 --- a/src/main/java/org/zwobble/couscous/ast/VariableReferenceNode.java +++ b/src/main/java/org/zwobble/couscous/ast/VariableReferenceNode.java @@ -6,7 +6,7 @@ import org.zwobble.couscous.ast.visitors.ExpressionNodeMapper; import java.util.function.Function; -public class VariableReferenceNode implements AssignableExpressionNode { +public class VariableReferenceNode implements AssignableExpressionNode, ReferenceNode { public static VariableReferenceNode reference(VariableNode variable) { return new VariableReferenceNode(variable.getDeclaration()); } @@ -49,6 +49,11 @@ public class VariableReferenceNode implements AssignableExpressionNode { mapper.visit(this); } + @Override + public T accept(Visitor visitor) { + return visitor.visit(this); + } + @Override public String toString() { return "VariableReferenceNode(referent=" + referent + ")"; diff --git a/src/main/java/org/zwobble/couscous/ast/structure/NodeStructure.java b/src/main/java/org/zwobble/couscous/ast/structure/NodeStructure.java index 6054711..47ba9b6 100644 --- a/src/main/java/org/zwobble/couscous/ast/structure/NodeStructure.java +++ b/src/main/java/org/zwobble/couscous/ast/structure/NodeStructure.java @@ -70,7 +70,7 @@ public class NodeStructure { } @Override - public Stream visit(ConstructorCallNode call) { + public Stream visit(ConstructorCallNode call) { return call.getArguments().stream(); } diff --git a/src/main/java/org/zwobble/couscous/backends/python/PythonCodeGenerator.java b/src/main/java/org/zwobble/couscous/backends/python/PythonCodeGenerator.java index 574854d..913b1b7 100644 --- a/src/main/java/org/zwobble/couscous/backends/python/PythonCodeGenerator.java +++ b/src/main/java/org/zwobble/couscous/backends/python/PythonCodeGenerator.java @@ -194,7 +194,7 @@ public class PythonCodeGenerator { return expression.accept(EXPRESSION_GENERATOR); } - private static List generateExpressions(Iterable expressions) { + private static List generateExpressions(Iterable expressions) { return ImmutableList.copyOf(Iterables.transform(expressions, PythonCodeGenerator::generateExpression)); } private static final ExpressionGenerator EXPRESSION_GENERATOR = new ExpressionGenerator(); diff --git a/src/main/java/org/zwobble/couscous/frontends/java/FreeVariables.java b/src/main/java/org/zwobble/couscous/frontends/java/FreeVariables.java index 99cc9b4..00184b1 100644 --- a/src/main/java/org/zwobble/couscous/frontends/java/FreeVariables.java +++ b/src/main/java/org/zwobble/couscous/frontends/java/FreeVariables.java @@ -1,9 +1,9 @@ package org.zwobble.couscous.frontends.java; -import com.google.common.collect.Sets; import org.zwobble.couscous.ast.*; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -12,19 +12,23 @@ import static org.zwobble.couscous.util.Casts.tryCast; import static org.zwobble.couscous.util.ExtraStreams.toStream; public class FreeVariables { - public static List findFreeVariables(List body) { - Stream referencedDeclarations = body.stream() - .flatMap(FreeVariables::findReferencedDeclarations); - Stream declarations = body.stream().flatMap(FreeVariables::findDeclarations); - return Sets.difference( - referencedDeclarations.collect(Collectors.toSet()), - declarations.collect(Collectors.toSet())).stream().collect(Collectors.toList()); + public static List findFreeVariables(List body) { + Set declarations = body.stream() + .flatMap(FreeVariables::findDeclarations) + .collect(Collectors.toSet()); + + return body.stream() + .flatMap(FreeVariables::findReferences) + .filter(reference -> + tryCast(VariableReferenceNode.class, reference) + .map(variableReference -> !declarations.contains(variableReference.getReferent())) + .orElse(true)) + .collect(Collectors.toList()); } - private static Stream findReferencedDeclarations(Node root) { - Stream references = descendantNodesAndSelf(root).flatMap(node -> - toStream(tryCast(VariableReferenceNode.class, node))); - return references.map(VariableReferenceNode::getReferent); + private static Stream findReferences(Node root) { + return descendantNodesAndSelf(root).flatMap(node -> + toStream(tryCast(ReferenceNode.class, node))); } private static Stream findDeclarations(Node root) { diff --git a/src/main/java/org/zwobble/couscous/frontends/java/GeneratedClosure.java b/src/main/java/org/zwobble/couscous/frontends/java/GeneratedClosure.java index 88d214c..9386ff6 100644 --- a/src/main/java/org/zwobble/couscous/frontends/java/GeneratedClosure.java +++ b/src/main/java/org/zwobble/couscous/frontends/java/GeneratedClosure.java @@ -1,6 +1,7 @@ package org.zwobble.couscous.frontends.java; import org.zwobble.couscous.ast.ClassNode; +import org.zwobble.couscous.ast.ReferenceNode; import org.zwobble.couscous.ast.TypeName; import org.zwobble.couscous.ast.VariableDeclaration; @@ -8,9 +9,9 @@ import java.util.List; public class GeneratedClosure { private final ClassNode classNode; - private final List captures; + private final List captures; - public GeneratedClosure(ClassNode classNode, List captures) { + public GeneratedClosure(ClassNode classNode, List captures) { this.classNode = classNode; this.captures = captures; } @@ -23,7 +24,7 @@ public class GeneratedClosure { return classNode.getName(); } - public List getCaptures() { + public List getCaptures() { return captures; } } diff --git a/src/main/java/org/zwobble/couscous/frontends/java/JavaExpressionReader.java b/src/main/java/org/zwobble/couscous/frontends/java/JavaExpressionReader.java index 13bc519..367a34f 100644 --- a/src/main/java/org/zwobble/couscous/frontends/java/JavaExpressionReader.java +++ b/src/main/java/org/zwobble/couscous/frontends/java/JavaExpressionReader.java @@ -17,7 +17,6 @@ import static org.zwobble.couscous.ast.MethodCallNode.not; import static org.zwobble.couscous.frontends.java.JavaOperators.readOperator; import static org.zwobble.couscous.frontends.java.JavaTypes.typeOf; import static org.zwobble.couscous.util.ExtraLists.concat; -import static org.zwobble.couscous.util.ExtraLists.eagerMap; public class JavaExpressionReader { private final Scope scope; @@ -232,8 +231,8 @@ public class JavaExpressionReader { captureArguments(closure)); } - private List captureArguments(GeneratedClosure closure) { - return eagerMap(closure.getCaptures(), VariableReferenceNode::reference); + private List captureArguments(GeneratedClosure closure) { + return closure.getCaptures(); } private List readArguments(IMethodBinding method, List javaArguments) { 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 1c53a44..5e81ec9 100644 --- a/src/main/java/org/zwobble/couscous/frontends/java/JavaReader.java +++ b/src/main/java/org/zwobble/couscous/frontends/java/JavaReader.java @@ -32,7 +32,6 @@ 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.*; -import static org.zwobble.couscous.util.Casts.tryCast; import static org.zwobble.couscous.util.ExtraLists.*; import static org.zwobble.couscous.util.ExtraMaps.toMap; @@ -109,51 +108,47 @@ public class JavaReader { private List replaceCaptureReferences( TypeName className, List body, - List freeVariables) + List freeVariables) { - Map freeVariablesById = Maps.transformValues( - Maps.uniqueIndex(freeVariables, VariableDeclaration::getId), + Map replacements = Maps.transformValues( + Maps.uniqueIndex(freeVariables, variable -> variable.freeVariable), freeVariable -> captureAccess(className, freeVariable)); - Function replaceExpression = new CaptureReplacer(freeVariablesById); + Function replaceExpression = new CaptureReplacer(replacements); return eagerMap(body, statement -> statement.replaceExpressions(replaceExpression)); } private class CaptureReplacer implements Function { - private final Map freeVariablesById; + private final Map replacements; - public CaptureReplacer(Map freeVariablesById) { - this.freeVariablesById = freeVariablesById; + public CaptureReplacer(Map replacements) { + this.replacements = replacements; } @Override public ExpressionNode apply(ExpressionNode expression) { - return tryCast(VariableReferenceNode.class, expression) - .flatMap(variableNode -> Optional.ofNullable(freeVariablesById.get(variableNode.getReferentId()))) + return Optional.ofNullable(replacements.get(expression)) .orElseGet(() -> expression.replaceExpressions(this)); } } - private ConstructorNode buildConstructor(Scope outerScope, TypeName type, List freeVariables) { + private ConstructorNode buildConstructor(Scope outerScope, TypeName type, List freeVariables) { Scope scope = outerScope.enterConstructor(); - Map argumentDeclarationsById = toMap( - freeVariables, - freeVariable -> immutableEntry( - freeVariable.getId(), - scope.generateVariable(freeVariable.getName(), freeVariable.getType()))); - List arguments = eagerMap( - freeVariables, - freeVariable -> formalArg(argumentDeclarationsById.get(freeVariable.getId()))); + ImmutableList.Builder arguments = ImmutableList.builder(); + ImmutableList.Builder body = ImmutableList.builder(); - List body = eagerMap(freeVariables, freeVariable -> assignStatement( - captureAccess(type, freeVariable), - reference(argumentDeclarationsById.get(freeVariable.getId())))); + for (CapturedVariable variable : freeVariables) { + FormalArgumentNode argument = scope.formalArgument(variable.field.getName(), variable.field.getType()); + arguments.add(argument); + body.add(assignStatement(captureAccess(type, variable), reference(argument))); + } - return constructor(arguments, body); + return constructor(arguments.build(), body.build()); } - private FieldAccessNode captureAccess(TypeName type, VariableDeclaration freeVariable) { - return fieldAccess(thisReference(type), freeVariable.getName(), freeVariable.getType()); + private FieldAccessNode captureAccess(TypeName type, CapturedVariable freeVariable) { + FieldDeclarationNode field = freeVariable.field; + return fieldAccess(thisReference(type), field.getName(), field.getType()); } GeneratedClosure readAnonymousClass(Scope outerScope, AnonymousClassDeclaration declaration) { @@ -170,6 +165,16 @@ public class JavaReader { return closure; } + private static class CapturedVariable { + private final ReferenceNode freeVariable; + private final FieldDeclarationNode field; + + public CapturedVariable(ReferenceNode freeVariable, FieldDeclarationNode field) { + this.freeVariable = freeVariable; + this.field = field; + } + } + private GeneratedClosure classWithCapture( Scope scope, TypeName className, @@ -177,10 +182,11 @@ public class JavaReader { List declaredFields, List methods ) { - List freeVariables = findFreeVariables(ExtraLists.concat(declaredFields, methods)); - Iterable captureFields = transform( + List freeVariables = findFreeVariables(ExtraLists.concat(declaredFields, methods)); + List capturedVariables = ImmutableList.copyOf(transform( freeVariables, - freeVariable -> field(freeVariable.getName(), freeVariable.getType())); + freeVariable -> new CapturedVariable(freeVariable, fieldForCapture(freeVariable)))); + Iterable captureFields = transform(capturedVariables, capture -> capture.field); List fields = ImmutableList.copyOf(concat(declaredFields, captureFields)); @@ -188,12 +194,28 @@ public class JavaReader { className, superTypes, fields, - buildConstructor(scope, className, freeVariables), + buildConstructor(scope, className, capturedVariables), eagerMap(methods, method -> - method.mapBody(body -> replaceCaptureReferences(className, body, freeVariables)))); + method.mapBody(body -> replaceCaptureReferences(className, body, capturedVariables)))); return new GeneratedClosure(classNode, freeVariables); } + private FieldDeclarationNode fieldForCapture(ReferenceNode freeVariable) { + return freeVariable.accept(new ReferenceNode.Visitor() { + @Override + public FieldDeclarationNode visit(VariableReferenceNode reference) { + return field(reference.getReferent().getName(), reference.getType()); + } + + @Override + public FieldDeclarationNode visit(ThisReferenceNode thisReference) { + TypeName type = thisReference.getType(); + String name = "this_" + type.getQualifiedName().replace(".", "__"); + return field(name, type); + } + }); + } + private TypeName generateAnonymousName(ASTNode node) { ITypeBinding type = findDeclaringClass(node); while (type.isAnonymous()) { diff --git a/src/main/java/org/zwobble/couscous/interpreter/Evaluator.java b/src/main/java/org/zwobble/couscous/interpreter/Evaluator.java index 548f731..3e01f36 100644 --- a/src/main/java/org/zwobble/couscous/interpreter/Evaluator.java +++ b/src/main/java/org/zwobble/couscous/interpreter/Evaluator.java @@ -108,7 +108,7 @@ public class Evaluator implements ExpressionNodeMapper { return left.getField(fieldAccess.getFieldName()); } - private List evalArguments(List arguments) { + private List evalArguments(List arguments) { return arguments.stream() .map(argument -> eval(argument)) .collect(Collectors.toList()); diff --git a/src/test/java/org/zwobble/couscous/tests/CompilerTests.java b/src/test/java/org/zwobble/couscous/tests/CompilerTests.java index 56d989c..6b0bf5f 100644 --- a/src/test/java/org/zwobble/couscous/tests/CompilerTests.java +++ b/src/test/java/org/zwobble/couscous/tests/CompilerTests.java @@ -1,6 +1,5 @@ package org.zwobble.couscous.tests; -import org.junit.Ignore; import org.junit.Test; import org.zwobble.couscous.ast.TypeName; import org.zwobble.couscous.values.PrimitiveValue; @@ -14,7 +13,6 @@ import java.nio.file.Path; import java.util.List; import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; import static org.junit.Assert.assertEquals; import static org.zwobble.couscous.tests.util.ExtraFiles.deleteRecursively; import static org.zwobble.couscous.values.PrimitiveValues.value; @@ -124,7 +122,6 @@ public abstract class CompilerTests { } @Test - @Ignore("WIP") public void lambdasWithThisCapture() throws Exception { assertEquals( value(42), diff --git a/src/test/java/org/zwobble/couscous/tests/frontends/java/FreeVariablesTests.java b/src/test/java/org/zwobble/couscous/tests/frontends/java/FreeVariablesTests.java index afded63..7cf349d 100644 --- a/src/test/java/org/zwobble/couscous/tests/frontends/java/FreeVariablesTests.java +++ b/src/test/java/org/zwobble/couscous/tests/frontends/java/FreeVariablesTests.java @@ -1,6 +1,7 @@ package org.zwobble.couscous.tests.frontends.java; import org.junit.Test; +import org.zwobble.couscous.ast.ReferenceNode; import org.zwobble.couscous.ast.VariableDeclaration; import org.zwobble.couscous.tests.TestIds; import org.zwobble.couscous.values.StringValue; @@ -21,14 +22,14 @@ public class FreeVariablesTests { @Test public void referenceIsFreeIfThereIsNoAssociatedDeclaration() { VariableDeclaration declaration = var(TestIds.ANY_ID, "[name]", StringValue.REF); - List freeVariables = findFreeVariables(asList(reference(declaration))); - assertEquals(asList(declaration), freeVariables); + List freeVariables = findFreeVariables(asList(reference(declaration))); + assertEquals(asList(reference(declaration)), freeVariables); } @Test public void referenceIsNotFreeIfIsDeclaredAsArgument() { VariableDeclaration declaration = var(TestIds.ANY_ID, "[name]", StringValue.REF); - List freeVariables = findFreeVariables( + List freeVariables = findFreeVariables( asList(formalArg(declaration), reference(declaration))); assertEquals(emptyList(), freeVariables); } @@ -36,7 +37,7 @@ public class FreeVariablesTests { @Test public void referenceIsNotFreeIfIsReferenceToLocalDefinedInBody() { VariableDeclaration declaration = var(TestIds.ANY_ID, "[name]", StringValue.REF); - List freeVariables = findFreeVariables( + List freeVariables = findFreeVariables( asList( localVariableDeclaration(declaration, literal("[value]")), reference(declaration))); -- cgit v1.2.3