aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/zwobble/couscous/transforms
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/zwobble/couscous/transforms')
-rw-r--r--src/main/java/org/zwobble/couscous/transforms/ClosureGenerator.java147
-rw-r--r--src/main/java/org/zwobble/couscous/transforms/HoistNestedTypes.java137
-rw-r--r--src/main/java/org/zwobble/couscous/transforms/SplitStaticsFromInterface.java6
3 files changed, 288 insertions, 2 deletions
diff --git a/src/main/java/org/zwobble/couscous/transforms/ClosureGenerator.java b/src/main/java/org/zwobble/couscous/transforms/ClosureGenerator.java
new file mode 100644
index 0000000..68c0aa6
--- /dev/null
+++ b/src/main/java/org/zwobble/couscous/transforms/ClosureGenerator.java
@@ -0,0 +1,147 @@
+package org.zwobble.couscous.transforms;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+import org.zwobble.couscous.ast.*;
+import org.zwobble.couscous.ast.visitors.NodeTransformer;
+import org.zwobble.couscous.frontends.java.GeneratedClosure;
+import org.zwobble.couscous.frontends.java.Scope;
+import org.zwobble.couscous.types.ScalarType;
+import org.zwobble.couscous.types.Type;
+import org.zwobble.couscous.types.TypeParameter;
+import org.zwobble.couscous.util.ExtraLists;
+import org.zwobble.couscous.util.InsertionOrderSet;
+
+import java.util.List;
+import java.util.Map;
+
+import static com.google.common.collect.Iterables.concat;
+import static com.google.common.collect.Iterables.transform;
+import static org.zwobble.couscous.ast.AssignmentNode.assignStatement;
+import static org.zwobble.couscous.ast.FieldAccessNode.fieldAccess;
+import static org.zwobble.couscous.ast.FieldDeclarationNode.field;
+import static org.zwobble.couscous.ast.FormalTypeParameterNode.formalTypeParameter;
+import static org.zwobble.couscous.ast.ThisReferenceNode.thisReference;
+import static org.zwobble.couscous.ast.VariableReferenceNode.reference;
+import static org.zwobble.couscous.frontends.java.FreeVariables.findFreeTypeParameters;
+import static org.zwobble.couscous.frontends.java.FreeVariables.findFreeVariables;
+import static org.zwobble.couscous.types.Types.erasure;
+import static org.zwobble.couscous.util.Casts.tryCast;
+import static org.zwobble.couscous.util.ExtraIterables.lazyMap;
+import static org.zwobble.couscous.util.ExtraLists.*;
+
+public class ClosureGenerator {
+ public static GeneratedClosure classWithCapture(
+ Scope scope,
+ ClassNode classNode
+ ) {
+ InsertionOrderSet<TypeParameter> freeTypeParameters = InsertionOrderSet.copyOf(
+ findFreeTypeParameters(classNode));
+
+ InsertionOrderSet<ReferenceNode> freeVariables = InsertionOrderSet.copyOf(Iterables.filter(
+ findFreeVariables(ExtraLists.concat(
+ classNode.getFields(),
+ classNode.getMethods())),
+ // TODO: check this references work correctly in anonymous classes
+ variable -> !isThisReference(classNode.getName(), variable)));
+ InsertionOrderSet<CapturedVariable> capturedVariables = InsertionOrderSet.copyOf(transform(
+ freeVariables,
+ freeVariable -> new CapturedVariable(freeVariable, fieldForCapture(freeVariable))));
+ Iterable<FieldDeclarationNode> captureFields = transform(capturedVariables, capture -> capture.field);
+
+ List<FieldDeclarationNode> fields = ImmutableList.copyOf(concat(
+ classNode.getFields(),
+ captureFields));
+
+ if (!classNode.getStaticConstructor().isEmpty()) {
+ throw new RuntimeException("Class has unexpected static constructor");
+ }
+
+ ClassNode generatedClass = new ClassNode(
+ classNode.getName(),
+ // TODO: generate fresh type parameter and replace in body
+ ExtraLists.concat(lazyMap(freeTypeParameters, parameter -> formalTypeParameter(parameter)), classNode.getTypeParameters()),
+ classNode.getSuperTypes(),
+ fields,
+ list(),
+ buildConstructor(scope, classNode.getName(), capturedVariables, classNode.getConstructor()),
+ eagerMap(classNode.getMethods(), method ->
+ method.mapBody(body -> replaceCaptureReferences(classNode.getName(), body, capturedVariables))),
+ list()
+ );
+ return new GeneratedClosure(generatedClass, freeTypeParameters, freeVariables);
+ }
+
+ private static boolean isThisReference(ScalarType name, ReferenceNode reference) {
+ return tryCast(ThisReferenceNode.class, reference)
+ .map(node -> node.getType().equals(name))
+ .orElse(false);
+ }
+
+ private static FieldDeclarationNode fieldForCapture(ReferenceNode freeVariable) {
+ return freeVariable.accept(new ReferenceNode.Visitor<FieldDeclarationNode>() {
+ @Override
+ public FieldDeclarationNode visit(VariableReferenceNode reference) {
+ return field(reference.getReferent().getName(), reference.getType());
+ }
+
+ @Override
+ public FieldDeclarationNode visit(ThisReferenceNode thisReference) {
+ Type type = thisReference.getType();
+ String name = "this_" + erasure(type).getQualifiedName().replace(".", "__");
+ return field(name, type);
+ }
+ });
+ }
+
+ private static List<StatementNode> replaceCaptureReferences(
+ ScalarType className,
+ List<StatementNode> body,
+ InsertionOrderSet<CapturedVariable> freeVariables)
+ {
+ Map<ExpressionNode, ExpressionNode> replacements = Maps.transformValues(
+ Maps.uniqueIndex(freeVariables, variable -> variable.freeVariable),
+ freeVariable -> captureAccess(className, freeVariable));
+ NodeTransformer transformer = NodeTransformer.replaceExpressions(replacements);
+ return eagerFlatMap(body, transformer::transformStatement);
+ }
+
+ private static ConstructorNode buildConstructor(
+ Scope outerScope,
+ ScalarType type,
+ InsertionOrderSet<CapturedVariable> freeVariables,
+ ConstructorNode existing)
+ {
+ Scope scope = outerScope.enterConstructor();
+
+ ImmutableList.Builder<FormalArgumentNode> arguments = ImmutableList.builder();
+ ImmutableList.Builder<StatementNode> body = ImmutableList.builder();
+
+ 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)));
+ }
+
+ arguments.addAll(existing.getArguments());
+ body.addAll(existing.getBody());
+
+ return new ConstructorNode(arguments.build(), body.build());
+ }
+
+ private static FieldAccessNode captureAccess(ScalarType type, CapturedVariable freeVariable) {
+ FieldDeclarationNode field = freeVariable.field;
+ return fieldAccess(thisReference(type), field.getName(), field.getType());
+ }
+
+ private static class CapturedVariable {
+ private final ReferenceNode freeVariable;
+ private final FieldDeclarationNode field;
+
+ public CapturedVariable(ReferenceNode freeVariable, FieldDeclarationNode field) {
+ this.freeVariable = freeVariable;
+ this.field = field;
+ }
+ }
+}
diff --git a/src/main/java/org/zwobble/couscous/transforms/HoistNestedTypes.java b/src/main/java/org/zwobble/couscous/transforms/HoistNestedTypes.java
new file mode 100644
index 0000000..e4ff1c8
--- /dev/null
+++ b/src/main/java/org/zwobble/couscous/transforms/HoistNestedTypes.java
@@ -0,0 +1,137 @@
+package org.zwobble.couscous.transforms;
+
+import org.zwobble.couscous.ast.ClassNode;
+import org.zwobble.couscous.ast.FormalArgumentNode;
+import org.zwobble.couscous.ast.MethodNode;
+import org.zwobble.couscous.ast.TypeNode;
+import org.zwobble.couscous.ast.visitors.NodeTransformer;
+import org.zwobble.couscous.frontends.java.GeneratedClosure;
+import org.zwobble.couscous.frontends.java.Scope;
+import org.zwobble.couscous.types.ScalarType;
+import org.zwobble.couscous.types.Type;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.regex.Pattern;
+
+import static org.zwobble.couscous.ast.ReturnNode.returns;
+import static org.zwobble.couscous.ast.VariableReferenceNode.reference;
+import static org.zwobble.couscous.util.Casts.tryCast;
+import static org.zwobble.couscous.util.ExtraIterables.iterable;
+import static org.zwobble.couscous.util.ExtraIterables.lazyMap;
+import static org.zwobble.couscous.util.ExtraLists.*;
+
+public class HoistNestedTypes {
+ public static List<TypeNode> hoist(List<TypeNode> declarations) {
+ return new HoistNestedTypes().hoistTypes(declarations);
+ }
+
+ private final Scope topScope = Scope.create().temporaryPrefix("_couscous_hoist_nested_types");
+
+ private List<TypeNode> hoistTypes(List<TypeNode> declarations) {
+ Iterable<TypeNode> types = eagerFlatMap(declarations, declaration -> hoistInnerTypes(declaration));
+
+ NodeTransformer referenceTransformer = NodeTransformer.builder()
+ .transformType(this::transformType)
+ .build();
+
+ return eagerFlatMap(types, referenceTransformer::transformTypeDeclaration);
+ }
+
+ private Type transformType(Type type) {
+ if (isInnerType(type)) {
+ return hoistedTypeName((ScalarType) type);
+ } else {
+ return type.transformSubTypes(this::transformType);
+ }
+ }
+
+ private static final Pattern INNER_TYPE_QUALIFIER_REGEX = Pattern.compile(".*\\.[A-Z][^.]*$");
+
+ private boolean isInnerType(Type type) {
+ // TODO: implement separate type classes for top-level and inner types rather than using this heuristic
+
+ return tryCast(ScalarType.class, type)
+ .flatMap(scalarType -> scalarType.getQualifier())
+ .map(qualifier -> INNER_TYPE_QUALIFIER_REGEX.matcher(qualifier).matches())
+ .orElse(false);
+ }
+
+ private static Type hoistedTypeName(ScalarType type) {
+ return ScalarType.of(type.getQualifier().get() + "__" + type.getSimpleName());
+ }
+
+ private Iterable<TypeNode> hoistInnerTypes(TypeNode declaration) {
+ TypeNode type = declaration.stripInnerTypes();
+ Iterable<HoistedType> hoistedTypes = eagerMap(declaration.getInnerTypes(), this::hoistType);
+
+ return cons(
+ addMethods(type, eagerFlatMap(hoistedTypes, hoisted -> iterable(hoisted.method))),
+ lazyMap(hoistedTypes, hoisted -> hoisted.type)
+ );
+ }
+
+ private static TypeNode addMethods(TypeNode declaration, List<MethodNode> methods) {
+ if (methods.isEmpty()) {
+ return declaration;
+ } else if (declaration instanceof ClassNode) {
+ ClassNode classDeclaration = (ClassNode) declaration;
+ return new ClassNode(
+ classDeclaration.getName(),
+ classDeclaration.getTypeParameters(),
+ classDeclaration.getSuperTypes(),
+ classDeclaration.getFields(),
+ classDeclaration.getStaticConstructor(),
+ classDeclaration.getConstructor(),
+ concat(classDeclaration.getMethods(), methods),
+ classDeclaration.getInnerTypes()
+ );
+ } else {
+ throw new UnsupportedOperationException("Cannot add methods to non-class node");
+ }
+ }
+
+ private HoistedType hoistType(TypeNode typeNode) {
+ // TODO: we need to hoist inner types of the inner types, specifically *after* the initial hoist (to make closures easier to generate)
+ // TODO: can we remove duplication of scope creation with readTypeDeclaration()?
+ Scope scope = topScope.enterClass(typeNode.getName());
+ return tryCast(ClassNode.class, typeNode)
+ .flatMap(node -> {
+ // TODO: don't generate closure if not necessary (i.e. it's an explicit or implicit static inner class)
+ GeneratedClosure closure = ClosureGenerator.classWithCapture(scope, node);
+// Type type = node.getTypeParameters().isEmpty()
+// ? typeNode.getName()
+// : parameterizedType(typeNode.getName(), eagerMap(node.getTypeParameters()));
+ if (closure.hasCaptures()) {
+ Type type = typeNode.getName();
+ // TODO: method type parameters
+ List<FormalArgumentNode> methodArguments = eagerMap(
+ node.getConstructor().getArguments(),
+ // TODO: use scope of outer class
+ argument -> scope.formalArgument(argument.getName(), argument.getType()));
+ MethodNode method = MethodNode.builder("create_" + typeNode.getName().getSimpleName())
+ .arguments(methodArguments)
+ .returns(type)
+ .statement(returns(closure.generateConstructor(lazyMap(methodArguments, argument -> reference(argument)))))
+ .build();
+ return Optional.of(new HoistedType(
+ closure.getClassNode(),
+ Optional.of(method)
+ ));
+ } else {
+ return Optional.empty();
+ }
+ })
+ .orElse(new HoistedType(typeNode, Optional.empty()));
+ }
+
+ private static class HoistedType {
+ private final TypeNode type;
+ private final Optional<MethodNode> method;
+
+ private HoistedType(TypeNode type, Optional<MethodNode> method) {
+ this.type = type;
+ this.method = method;
+ }
+ }
+}
diff --git a/src/main/java/org/zwobble/couscous/transforms/SplitStaticsFromInterface.java b/src/main/java/org/zwobble/couscous/transforms/SplitStaticsFromInterface.java
index b2c88ea..3af4042 100644
--- a/src/main/java/org/zwobble/couscous/transforms/SplitStaticsFromInterface.java
+++ b/src/main/java/org/zwobble/couscous/transforms/SplitStaticsFromInterface.java
@@ -27,7 +27,8 @@ public class SplitStaticsFromInterface {
interfaceNode.getFields(),
interfaceNode.getStaticConstructor(),
ConstructorNode.DEFAULT,
- eagerFilter(interfaceNode.getMethods(), method -> method.isStatic())
+ eagerFilter(interfaceNode.getMethods(), method -> method.isStatic()),
+ list()
));
result.add(InterfaceNode.declareInterface(
interfaceNode.getName(),
@@ -35,7 +36,8 @@ public class SplitStaticsFromInterface {
interfaceNode.getSuperTypes(),
list(),
list(),
- eagerFilter(interfaceNode.getMethods(), method -> !method.isStatic())
+ eagerFilter(interfaceNode.getMethods(), method -> !method.isStatic()),
+ interfaceNode.getInnerTypes()
));
} else {
result.add(typeNode);