aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/java/org/zwobble/couscous/ast/ClassNode.java2
-rw-r--r--src/main/java/org/zwobble/couscous/ast/InterfaceNode.java1
-rw-r--r--src/main/java/org/zwobble/couscous/ast/TypeNode.java3
-rw-r--r--src/main/java/org/zwobble/couscous/frontends/java/JavaReader.java84
-rw-r--r--src/main/java/org/zwobble/couscous/util/ExtraMaps.java5
-rw-r--r--src/test/java/org/zwobble/couscous/tests/CompilerTests.java12
-rw-r--r--src/test/resources/java/inner-class/com/example/InnerClass.java24
7 files changed, 107 insertions, 24 deletions
diff --git a/src/main/java/org/zwobble/couscous/ast/ClassNode.java b/src/main/java/org/zwobble/couscous/ast/ClassNode.java
index 5d6d5e2..4cdb993 100644
--- a/src/main/java/org/zwobble/couscous/ast/ClassNode.java
+++ b/src/main/java/org/zwobble/couscous/ast/ClassNode.java
@@ -13,6 +13,7 @@ public class ClassNode implements TypeNode {
public static ClassNodeBuilder builder(String name) {
return new ClassNodeBuilder(name);
}
+
public static ClassNode declareClass(
TypeName name,
Set<TypeName> superTypes,
@@ -82,6 +83,7 @@ public class ClassNode implements TypeNode {
return visitor.visit(this);
}
+ @Override
public ClassNode transform(NodeTransformer transformer) {
return new ClassNode(
transformer.transform(name),
diff --git a/src/main/java/org/zwobble/couscous/ast/InterfaceNode.java b/src/main/java/org/zwobble/couscous/ast/InterfaceNode.java
index 4ae43c8..854728f 100644
--- a/src/main/java/org/zwobble/couscous/ast/InterfaceNode.java
+++ b/src/main/java/org/zwobble/couscous/ast/InterfaceNode.java
@@ -49,6 +49,7 @@ public class InterfaceNode implements TypeNode {
return visitor.visit(this);
}
+ @Override
public InterfaceNode transform(NodeTransformer transformer) {
return new InterfaceNode(
transformer.transform(name),
diff --git a/src/main/java/org/zwobble/couscous/ast/TypeNode.java b/src/main/java/org/zwobble/couscous/ast/TypeNode.java
index 9946267..8277cda 100644
--- a/src/main/java/org/zwobble/couscous/ast/TypeNode.java
+++ b/src/main/java/org/zwobble/couscous/ast/TypeNode.java
@@ -1,5 +1,7 @@
package org.zwobble.couscous.ast;
+import org.zwobble.couscous.ast.visitors.NodeTransformer;
+
import java.util.List;
import java.util.Set;
@@ -7,4 +9,5 @@ public interface TypeNode extends Node {
TypeName getName();
Set<TypeName> getSuperTypes();
List<MethodNode> getMethods();
+ TypeNode transform(NodeTransformer transformer);
}
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 d56b091..dd83b8c 100644
--- a/src/main/java/org/zwobble/couscous/frontends/java/JavaReader.java
+++ b/src/main/java/org/zwobble/couscous/frontends/java/JavaReader.java
@@ -9,15 +9,11 @@ import org.zwobble.couscous.ast.sugar.AnonymousClass;
import org.zwobble.couscous.ast.sugar.Lambda;
import org.zwobble.couscous.ast.sugar.TypeDeclarationBody;
import org.zwobble.couscous.ast.visitors.NodeTransformer;
-import org.zwobble.couscous.util.Casts;
import org.zwobble.couscous.util.ExtraLists;
import java.io.IOException;
import java.nio.file.Path;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
+import java.util.*;
import java.util.function.Function;
import static com.google.common.collect.Iterables.concat;
@@ -35,7 +31,9 @@ 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.lookup;
public class JavaReader {
public static List<TypeNode> readClassFromFile(List<Path> sourcePaths, Path sourcePath) throws IOException {
@@ -46,7 +44,7 @@ public class JavaReader {
throw new RuntimeException("Errors during parsing:\n\n" + describe(errors));
}
JavaReader reader = new JavaReader();
- return cons(reader.readCompilationUnit(ast), reader.classes.build());
+ return reader.readCompilationUnit(ast);
}
private static String describe(List<IProblem> errors) {
@@ -62,14 +60,23 @@ public class JavaReader {
private final ImmutableList.Builder<TypeNode> classes;
private int anonymousClassCount = 0;
private final Scope topScope = Scope.create();
+ private final Map<TypeName, TypeName> nestedClasses = new HashMap<>();
private JavaReader() {
classes = ImmutableList.builder();
}
- private TypeNode readCompilationUnit(CompilationUnit ast) {
+ private List<TypeNode> readCompilationUnit(CompilationUnit ast) {
TypeDeclaration type = (TypeDeclaration)ast.types().get(0);
- return readTypeDeclaration(type);
+ List<TypeNode> classes = cons(readTypeDeclaration(type), this.classes.build());
+ NodeTransformer transformer = NodeTransformer.builder()
+ .transformExpression(expression ->
+ tryCast(ConstructorCallNode.class, expression)
+ .flatMap(call -> lookup(nestedClasses, call.getType()).map(outerType ->
+ // TODO: handle existing arguments
+ new ConstructorCallNode(call.getType(), list(thisReference(outerType))))))
+ .build();
+ return eagerMap(classes, classNode -> classNode.transform(transformer));
}
private TypeNode readTypeDeclaration(TypeDeclaration type) {
@@ -188,30 +195,45 @@ public class JavaReader {
private GeneratedClosure classWithCapture(
Scope scope,
- TypeName className,
- AnonymousClass anonymousClass
+ ClassNode classNode
) {
List<ReferenceNode> freeVariables = findFreeVariables(ExtraLists.concat(
- anonymousClass.getFields(),
- anonymousClass.getMethods()));
+ classNode.getFields(),
+ classNode.getMethods()));
List<CapturedVariable> capturedVariables = ImmutableList.copyOf(transform(
freeVariables,
freeVariable -> new CapturedVariable(freeVariable, fieldForCapture(freeVariable))));
Iterable<FieldDeclarationNode> captureFields = transform(capturedVariables, capture -> capture.field);
List<FieldDeclarationNode> fields = ImmutableList.copyOf(concat(
- anonymousClass.getFields(),
+ classNode.getFields(),
captureFields));
- ClassNode classNode = ClassNode.declareClass(
+ ClassNode generatedClass = ClassNode.declareClass(
+ classNode.getName(),
+ classNode.getSuperTypes(),
+ fields,
+ // TODO: handle static constructor (or codify that this is a case that never needs handling)
+ list(),
+ // TODO: handle existing constructor
+ buildConstructor(scope, classNode.getName(), capturedVariables),
+ eagerMap(classNode.getMethods(), method ->
+ method.mapBody(body -> replaceCaptureReferences(classNode.getName(), body, capturedVariables))));
+ return new GeneratedClosure(generatedClass, freeVariables);
+ }
+
+ private GeneratedClosure classWithCapture(
+ Scope scope,
+ TypeName className,
+ AnonymousClass anonymousClass
+ ) {
+ return classWithCapture(scope, ClassNode.declareClass(
className,
anonymousClass.getSuperTypes(),
- fields,
+ anonymousClass.getFields(),
list(),
- buildConstructor(scope, className, capturedVariables),
- eagerMap(anonymousClass.getMethods(), method ->
- method.mapBody(body -> replaceCaptureReferences(className, body, capturedVariables))));
- return new GeneratedClosure(classNode, freeVariables);
+ ConstructorNode.DEFAULT,
+ anonymousClass.getMethods()));
}
private FieldDeclarationNode fieldForCapture(ReferenceNode freeVariable) {
@@ -249,23 +271,37 @@ public class JavaReader {
TypeDeclarationBody.Builder body = TypeDeclarationBody.builder();
for (Object declaration : bodyDeclarations) {
- Casts.tryCast(MethodDeclaration.class, declaration)
+ tryCast(MethodDeclaration.class, declaration)
.ifPresent(method -> readMethod(body, scope, method));
- Casts.tryCast(Initializer.class, declaration)
+ tryCast(Initializer.class, declaration)
.ifPresent(initializer -> body.addInitializer(
Modifier.isStatic(initializer.getModifiers()),
readStatement(scope, initializer.getBody())));
- Casts.tryCast(FieldDeclaration.class, declaration)
+ tryCast(FieldDeclaration.class, declaration)
.ifPresent(field -> readField(body, scope, type, field));
- Casts.tryCast(TypeDeclaration.class, declaration)
- .ifPresent(typeDeclaration -> classes.add(readTypeDeclaration(typeDeclaration)));
+ tryCast(TypeDeclaration.class, declaration)
+ .ifPresent(typeDeclaration -> classes.add(readNestedTypeDeclaration(type, typeDeclaration)));
}
return body.build();
}
+ private TypeNode readNestedTypeDeclaration(TypeName outerType, TypeDeclaration typeDeclaration) {
+ TypeNode typeNode = readTypeDeclaration(typeDeclaration);
+ // TODO: can we remove duplication of scope creation with readTypeDeclaration()?
+ Scope scope = topScope.enterClass(typeNode.getName());
+ return tryCast(ClassNode.class, typeNode)
+ .filter(node -> !Modifier.isStatic(typeDeclaration.getModifiers()))
+ .<TypeNode>map(node -> {
+ GeneratedClosure closure = classWithCapture(scope, node);
+ nestedClasses.put(node.getName(), outerType);
+ return closure.getClassNode();
+ })
+ .orElse(typeNode);
+ }
+
private void readField(TypeDeclarationBody.Builder builder, Scope scope, TypeName declaringType, FieldDeclaration field) {
@SuppressWarnings("unchecked")
List<VariableDeclarationFragment> fragments = field.fragments();
diff --git a/src/main/java/org/zwobble/couscous/util/ExtraMaps.java b/src/main/java/org/zwobble/couscous/util/ExtraMaps.java
index 09339e6..55829cf 100644
--- a/src/main/java/org/zwobble/couscous/util/ExtraMaps.java
+++ b/src/main/java/org/zwobble/couscous/util/ExtraMaps.java
@@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableMap;
import java.util.AbstractMap;
import java.util.Map;
+import java.util.Optional;
import java.util.function.Function;
public class ExtraMaps {
@@ -23,6 +24,10 @@ public class ExtraMaps {
return ImmutableMap.of(key1, value1, key2, value2, key3, value3);
}
+ public static <K, V> Optional<V> lookup(Map<K, V> map, K key) {
+ return Optional.ofNullable(map.get(key));
+ }
+
public static <T, K, V> Map<K, V> toMap(Iterable<T> iterable, Function<T, Map.Entry<K, V>> function) {
ImmutableMap.Builder<K, V> builder = ImmutableMap.builder();
diff --git a/src/test/java/org/zwobble/couscous/tests/CompilerTests.java b/src/test/java/org/zwobble/couscous/tests/CompilerTests.java
index 285a51f..3641f33 100644
--- a/src/test/java/org/zwobble/couscous/tests/CompilerTests.java
+++ b/src/test/java/org/zwobble/couscous/tests/CompilerTests.java
@@ -99,6 +99,18 @@ public abstract class CompilerTests {
}
@Test
+ public void innerClasses() throws Exception {
+ assertEquals(
+ value(42),
+ execTestProgram(
+ "inner-class",
+ TypeName.of("com.example.InnerClass"),
+ "run",
+ list(),
+ IntegerValue.REF));
+ }
+
+ @Test
public void canImplementUserDefinedInterface() throws Exception {
assertEquals(
value(42),
diff --git a/src/test/resources/java/inner-class/com/example/InnerClass.java b/src/test/resources/java/inner-class/com/example/InnerClass.java
new file mode 100644
index 0000000..ccf8edb
--- /dev/null
+++ b/src/test/resources/java/inner-class/com/example/InnerClass.java
@@ -0,0 +1,24 @@
+package com.example;
+
+public class InnerClass {
+ private final int stash;
+
+ public static int run() {
+ return new InnerClass(42).value();
+ }
+
+ public InnerClass(int stash) {
+ this.stash = stash;
+ }
+
+ public int value() {
+ IntSupplier supplier = new IntSupplier();
+ return supplier.get();
+ }
+
+ private class IntSupplier {
+ public int get() {
+ return stash;
+ }
+ }
+}