From 7df6bc37bfac33770b9758647ab73822d807f0fe Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Thu, 11 Feb 2016 20:52:41 +0000 Subject: Implement static fields and static constructors for C# --- .../java/org/zwobble/couscous/ast/ClassNode.java | 74 +++++++++++++--------- .../org/zwobble/couscous/ast/ClassNodeBuilder.java | 14 ++++ .../zwobble/couscous/ast/FieldDeclarationNode.java | 70 +++++++++++--------- .../couscous/backends/csharp/CsharpSerializer.java | 19 ++++++ .../couscous/frontends/java/JavaReader.java | 2 + .../zwobble/couscous/tests/BackendEvalTests.java | 18 ++++++ .../backends/csharp/CsharpSerializerTests.java | 26 +++++++- .../backends/python/PythonCompilerEvalTests.java | 7 ++ .../couscous/tests/interpreter/EvaluatorTests.java | 6 ++ 9 files changed, 173 insertions(+), 63 deletions(-) diff --git a/src/main/java/org/zwobble/couscous/ast/ClassNode.java b/src/main/java/org/zwobble/couscous/ast/ClassNode.java index ac304e6..0aaa8c1 100644 --- a/src/main/java/org/zwobble/couscous/ast/ClassNode.java +++ b/src/main/java/org/zwobble/couscous/ast/ClassNode.java @@ -7,6 +7,9 @@ import java.util.List; import java.util.Set; public class ClassNode implements Node { + public static ClassNodeBuilder builder(TypeName name) { + return new ClassNodeBuilder(name); + } public static ClassNodeBuilder builder(String name) { return new ClassNodeBuilder(name); } @@ -14,26 +17,31 @@ public class ClassNode implements Node { TypeName name, Set superTypes, List fields, + List staticConstructor, ConstructorNode constructor, List methods) { - return new ClassNode(name, superTypes, fields, constructor, methods); + return new ClassNode(name, superTypes, fields, staticConstructor, constructor, methods); } private final TypeName name; private final Set superTypes; private final List fields; + private final List staticConstructor; private final ConstructorNode constructor; private final List methods; private ClassNode( - TypeName name, - Set superTypes, - List fields, - ConstructorNode constructor, - List methodNodes) { + TypeName name, + Set superTypes, + List fields, + List staticConstructor, + ConstructorNode constructor, + List methodNodes) + { this.name = name; this.superTypes = superTypes; this.fields = fields; + this.staticConstructor = staticConstructor; this.constructor = constructor; methods = methodNodes; } @@ -49,7 +57,11 @@ public class ClassNode implements Node { public List getFields() { return fields; } - + + public List getStaticConstructor() { + return staticConstructor; + } + public ConstructorNode getConstructor() { return constructor; } @@ -72,21 +84,10 @@ public class ClassNode implements Node { transformer.transform(name), transformer.transformTypes(superTypes), transformer.transformFields(fields), - transformer.transformConstructor(constructor), + staticConstructor, transformer.transformConstructor(constructor), transformer.transformMethods(methods)); } - @Override - public String toString() { - return "ClassNode(" + - "name=" + name + - ", superTypes=" + superTypes + - ", fields=" + fields + - ", constructor=" + constructor + - ", methods=" + methods + - ')'; - } - @Override public boolean equals(Object o) { if (this == o) return true; @@ -94,22 +95,35 @@ public class ClassNode implements Node { ClassNode classNode = (ClassNode) o; - if (name != null ? !name.equals(classNode.name) : classNode.name != null) return false; - if (superTypes != null ? !superTypes.equals(classNode.superTypes) : classNode.superTypes != null) return false; - if (fields != null ? !fields.equals(classNode.fields) : classNode.fields != null) return false; - if (constructor != null ? !constructor.equals(classNode.constructor) : classNode.constructor != null) - return false; - return methods != null ? methods.equals(classNode.methods) : classNode.methods == null; + if (!name.equals(classNode.name)) return false; + if (!superTypes.equals(classNode.superTypes)) return false; + if (!fields.equals(classNode.fields)) return false; + if (!staticConstructor.equals(classNode.staticConstructor)) return false; + if (!constructor.equals(classNode.constructor)) return false; + return methods.equals(classNode.methods); } @Override public int hashCode() { - int result = name != null ? name.hashCode() : 0; - result = 31 * result + (superTypes != null ? superTypes.hashCode() : 0); - result = 31 * result + (fields != null ? fields.hashCode() : 0); - result = 31 * result + (constructor != null ? constructor.hashCode() : 0); - result = 31 * result + (methods != null ? methods.hashCode() : 0); + int result = name.hashCode(); + result = 31 * result + superTypes.hashCode(); + result = 31 * result + fields.hashCode(); + result = 31 * result + staticConstructor.hashCode(); + result = 31 * result + constructor.hashCode(); + result = 31 * result + methods.hashCode(); return result; } + + @Override + public String toString() { + return "ClassNode(" + + "name=" + name + + ", superTypes=" + superTypes + + ", fields=" + fields + + ", staticConstructor=" + staticConstructor + + ", constructor=" + constructor + + ", methods=" + methods + + ')'; + } } diff --git a/src/main/java/org/zwobble/couscous/ast/ClassNodeBuilder.java b/src/main/java/org/zwobble/couscous/ast/ClassNodeBuilder.java index f1240f6..a4a268a 100644 --- a/src/main/java/org/zwobble/couscous/ast/ClassNodeBuilder.java +++ b/src/main/java/org/zwobble/couscous/ast/ClassNodeBuilder.java @@ -4,22 +4,26 @@ import com.google.common.collect.ImmutableList; import org.zwobble.couscous.ast.identifiers.Identifier; import org.zwobble.couscous.values.UnitValue; +import java.util.List; import java.util.Optional; import java.util.function.Function; import static java.util.Collections.emptySet; import static org.zwobble.couscous.ast.FormalArgumentNode.formalArg; import static org.zwobble.couscous.ast.VariableDeclaration.var; +import static org.zwobble.couscous.util.ExtraLists.list; public class ClassNodeBuilder { private final TypeName name; private final ImmutableList.Builder fields; + private List staticConstructor; private Optional constructor; private final ImmutableList.Builder methods; public ClassNodeBuilder(TypeName name) { this.name = name; this.fields = ImmutableList.builder(); + staticConstructor = list(); this.constructor = Optional.empty(); this.methods = ImmutableList.builder(); } @@ -28,6 +32,10 @@ public class ClassNodeBuilder { this(TypeName.of(name)); } + public ClassNodeBuilder staticField(String name, TypeName type) { + return field(FieldDeclarationNode.staticField(name, type)); + } + public ClassNodeBuilder field(String name, TypeName type) { return field(FieldDeclarationNode.field(name, type)); } @@ -46,6 +54,11 @@ public class ClassNodeBuilder { return constructor(build.apply(constructorBuilder()).build()); } + public ClassNodeBuilder staticConstructor(List body) { + this.staticConstructor = body; + return this; + } + private MethodBuilder constructorBuilder() { return new MethodBuilder<>(builder -> ConstructorNode.constructor( @@ -86,6 +99,7 @@ public class ClassNodeBuilder { name, emptySet(), fields.build(), + staticConstructor, constructor.orElse(ConstructorNode.DEFAULT), methods.build()); } diff --git a/src/main/java/org/zwobble/couscous/ast/FieldDeclarationNode.java b/src/main/java/org/zwobble/couscous/ast/FieldDeclarationNode.java index 3b27b9e..a9f9b76 100644 --- a/src/main/java/org/zwobble/couscous/ast/FieldDeclarationNode.java +++ b/src/main/java/org/zwobble/couscous/ast/FieldDeclarationNode.java @@ -5,17 +5,31 @@ import org.zwobble.couscous.ast.visitors.NodeTransformer; public class FieldDeclarationNode implements Node { public static FieldDeclarationNode field(String name, TypeName type) { - return new FieldDeclarationNode(name, type); + return field(false, name, type); } - + + public static FieldDeclarationNode staticField(String name, TypeName type) { + return field(true, name, type); + } + + public static FieldDeclarationNode field(boolean isStatic, String name, TypeName type) { + return new FieldDeclarationNode(isStatic, name, type); + } + + private final boolean isStatic; private final String name; private final TypeName type; - private FieldDeclarationNode(String name, TypeName type) { + private FieldDeclarationNode(boolean isStatic, String name, TypeName type) { + this.isStatic = isStatic; this.name = name; this.type = type; } - + + public boolean isStatic() { + return isStatic; + } + public String getName() { return name; } @@ -31,43 +45,37 @@ public class FieldDeclarationNode implements Node { public FieldDeclarationNode transform(NodeTransformer transformer) { return new FieldDeclarationNode( - name, + isStatic, name, transformer.transform(type)); } @Override public String toString() { - return "FieldDeclarationNode(name=" + name + ", type=" + type + ")"; + return "FieldDeclarationNode(" + + "isStatic=" + isStatic + + ", name=" + name + + ", type=" + type + + ')'; } @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((name == null) ? 0 : name.hashCode()); - result = prime * result + ((type == null) ? 0 : type.hashCode()); - return result; + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + FieldDeclarationNode that = (FieldDeclarationNode) o; + + if (isStatic != that.isStatic) return false; + if (!name.equals(that.name)) return false; + return type.equals(that.type); + } @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - FieldDeclarationNode other = (FieldDeclarationNode) obj; - if (name == null) { - if (other.name != null) - return false; - } else if (!name.equals(other.name)) - return false; - if (type == null) { - if (other.type != null) - return false; - } else if (!type.equals(other.type)) - return false; - return true; + public int hashCode() { + int result = (isStatic ? 1 : 0); + result = 31 * result + name.hashCode(); + result = 31 * result + type.hashCode(); + return result; } } diff --git a/src/main/java/org/zwobble/couscous/backends/csharp/CsharpSerializer.java b/src/main/java/org/zwobble/couscous/backends/csharp/CsharpSerializer.java index a6bcfc9..c63bbbb 100644 --- a/src/main/java/org/zwobble/couscous/backends/csharp/CsharpSerializer.java +++ b/src/main/java/org/zwobble/couscous/backends/csharp/CsharpSerializer.java @@ -8,6 +8,8 @@ import org.zwobble.couscous.values.UnitValue; import java.util.List; +import static org.zwobble.couscous.util.ExtraLists.list; + public class CsharpSerializer implements NodeVisitor { public static String serialize(Node node) { SourceCodeWriter writer = new SourceCodeWriter( @@ -286,6 +288,10 @@ public class CsharpSerializer implements NodeVisitor { writer.writeStatement(() -> { writer.writeKeyword("internal"); writer.writeSpace(); + if (declaration.isStatic()) { + writer.writeKeyword("static"); + writer.writeSpace(); + } writeTypeReference(declaration.getType()); writer.writeSpace(); writer.writeIdentifier(declaration.getName()); @@ -308,6 +314,7 @@ public class CsharpSerializer implements NodeVisitor { writer.writeIdentifier(classNode.getSimpleName()); writer.startBlock(); writeAll(classNode.getFields()); + writeStaticConstructor(classNode); writeConstructor(classNode, classNode.getConstructor()); writeAll(classNode.getMethods()); writer.endBlock(); @@ -317,6 +324,18 @@ public class CsharpSerializer implements NodeVisitor { }); } + private void writeStaticConstructor(ClassNode classNode) { + if (!classNode.getStaticConstructor().isEmpty()) { + writer.writeStatement(() -> { + writer.writeKeyword("static"); + writer.writeSpace(); + writer.writeIdentifier(classNode.getSimpleName()); + writeFormalArguments(list()); + writeBlock(classNode.getStaticConstructor()); + }); + } + } + private void writeConstructor(ClassNode classNode, ConstructorNode constructor) { if (!constructor.equals(ConstructorNode.DEFAULT)) { writer.writeStatement(() -> { 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 2067381..ca0f87a 100644 --- a/src/main/java/org/zwobble/couscous/frontends/java/JavaReader.java +++ b/src/main/java/org/zwobble/couscous/frontends/java/JavaReader.java @@ -70,6 +70,7 @@ public class JavaReader { name, superTypes(type), body.getFields(), + list(), body.getConstructor(), body.getMethods()); } @@ -190,6 +191,7 @@ public class JavaReader { className, anonymousClass.getSuperTypes(), fields, + list(), buildConstructor(scope, className, capturedVariables), eagerMap(anonymousClass.getMethods(), method -> method.mapBody(body -> replaceCaptureReferences(className, body, capturedVariables)))); diff --git a/src/test/java/org/zwobble/couscous/tests/BackendEvalTests.java b/src/test/java/org/zwobble/couscous/tests/BackendEvalTests.java index 41ce893..9914093 100644 --- a/src/test/java/org/zwobble/couscous/tests/BackendEvalTests.java +++ b/src/test/java/org/zwobble/couscous/tests/BackendEvalTests.java @@ -225,6 +225,24 @@ public abstract class BackendEvalTests { assertEquals(value(42), result); } + + @Test + public void staticConstructorIsExecutedOnReference() { + TypeName type = TypeName.of("com.example.Example"); + ClassNode classNode = ClassNode.builder(type) + .staticField("value", IntegerValue.REF) + .staticConstructor(list( + assignStatement( + fieldAccess(type, "value", IntegerValue.REF), + literal(42)))) + .build(); + + PrimitiveValue result = evalExpression( + list(classNode), + fieldAccess(type, "value", IntegerValue.REF)); + + assertEquals(value(42), result); + } private PrimitiveValue evalExpression(ExpressionNode expression) { return evalExpression(list(), expression); diff --git a/src/test/java/org/zwobble/couscous/tests/backends/csharp/CsharpSerializerTests.java b/src/test/java/org/zwobble/couscous/tests/backends/csharp/CsharpSerializerTests.java index 6bc191c..635afca 100644 --- a/src/test/java/org/zwobble/couscous/tests/backends/csharp/CsharpSerializerTests.java +++ b/src/test/java/org/zwobble/couscous/tests/backends/csharp/CsharpSerializerTests.java @@ -148,6 +148,15 @@ public class CsharpSerializerTests { assertEquals("x.y", output); } + @Test + public void staticFieldAccessSeparatesReceiverAndNameWithDot() { + String output = serialize(fieldAccess( + TypeName.of("X"), + "y", + TypeName.of("Y"))); + assertEquals("X.y", output); + } + @Test public void returnStatementUsesReturnStatement() { String output = serialize(returns(literal(true))); @@ -253,6 +262,18 @@ public class CsharpSerializerTests { assertEquals("namespace com.example {\n internal class Example {\n }\n}\n", output); } + @Test + public void staticConstructorHasSerializedBody() { + ClassNode classNode = ClassNode.builder("com.example.Example") + .staticConstructor(list(expressionStatement(literal(true)))) + .build(); + + String output = serialize(classNode); + + assertEquals( + "namespace com.example {\n internal class Example {\n static Example() {\n true;\n }\n }\n}\n", output); + } + @Test public void constructorHasSerializedBody() { ConstructorNode constructor = ConstructorNode.constructor( @@ -271,12 +292,13 @@ public class CsharpSerializerTests { @Test public void classWithFields() { ClassNode classNode = ClassNode.builder("com.example.Example") - .field("x", TypeName.of("X")) + .staticField("x", TypeName.of("X")) + .field("y", TypeName.of("Y")) .build(); String output = serialize(classNode); - assertEquals("namespace com.example {\n internal class Example {\n internal X x;\n }\n}\n", output); + assertEquals("namespace com.example {\n internal class Example {\n internal static X x;\n internal Y y;\n }\n}\n", output); } private String serialize(Node node) { diff --git a/src/test/java/org/zwobble/couscous/tests/backends/python/PythonCompilerEvalTests.java b/src/test/java/org/zwobble/couscous/tests/backends/python/PythonCompilerEvalTests.java index e4ff1cb..6337bc7 100644 --- a/src/test/java/org/zwobble/couscous/tests/backends/python/PythonCompilerEvalTests.java +++ b/src/test/java/org/zwobble/couscous/tests/backends/python/PythonCompilerEvalTests.java @@ -1,5 +1,7 @@ package org.zwobble.couscous.tests.backends.python; +import org.junit.Ignore; +import org.junit.Test; import org.zwobble.couscous.ast.ClassNode; import org.zwobble.couscous.ast.ExpressionNode; import org.zwobble.couscous.tests.BackendEvalTests; @@ -8,6 +10,11 @@ import org.zwobble.couscous.values.PrimitiveValue; import java.util.List; public class PythonCompilerEvalTests extends BackendEvalTests { + @Ignore + @Test + public void staticConstructorIsExecutedOnReference() { + } + @Override protected PrimitiveValue evalExpression(List classes, ExpressionNode expression) { return new PythonMethodRunner().evalExpression(classes, expression); diff --git a/src/test/java/org/zwobble/couscous/tests/interpreter/EvaluatorTests.java b/src/test/java/org/zwobble/couscous/tests/interpreter/EvaluatorTests.java index afd0936..21bc88f 100644 --- a/src/test/java/org/zwobble/couscous/tests/interpreter/EvaluatorTests.java +++ b/src/test/java/org/zwobble/couscous/tests/interpreter/EvaluatorTests.java @@ -1,6 +1,7 @@ package org.zwobble.couscous.tests.interpreter; import com.google.common.collect.ImmutableMap; +import org.junit.Ignore; import org.junit.Test; import org.zwobble.couscous.ast.ClassNode; import org.zwobble.couscous.ast.ExpressionNode; @@ -38,6 +39,11 @@ import static org.zwobble.couscous.tests.util.ExtraAsserts.assertThrows; import static org.zwobble.couscous.util.ExtraLists.list; public class EvaluatorTests extends BackendEvalTests { + @Ignore + @Test + public void staticConstructorIsExecutedOnReference() { + } + @Test public void valueOfAssignmentExpressionIsNewValue() { FormalArgumentNode arg = formalArg(var(ANY_ID, "x", StringValue.REF)); -- cgit v1.2.3