diff options
Diffstat (limited to 'src/main/java/org/zwobble/couscous/transforms')
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); |
