aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/org/zwobble/couscous/ast/ClassNode.java5
-rw-r--r--src/main/java/org/zwobble/couscous/backends/python/PythonCodeGenerator.java121
-rw-r--r--src/main/java/org/zwobble/couscous/backends/python/PythonCompiler.java34
-rw-r--r--src/main/java/org/zwobble/couscous/backends/python/PythonSerializer.java107
-rw-r--r--src/main/java/org/zwobble/couscous/backends/python/ast/PythonBlock.java25
-rw-r--r--src/main/java/org/zwobble/couscous/backends/python/ast/PythonClassNode.java48
-rw-r--r--src/main/java/org/zwobble/couscous/backends/python/ast/PythonExpressionNode.java5
-rw-r--r--src/main/java/org/zwobble/couscous/backends/python/ast/PythonFunctionDefinitionNode.java22
-rw-r--r--src/main/java/org/zwobble/couscous/backends/python/ast/PythonIntegerLiteralNode.java15
-rw-r--r--src/main/java/org/zwobble/couscous/backends/python/ast/PythonModuleNode.java18
-rw-r--r--src/main/java/org/zwobble/couscous/backends/python/ast/PythonNode.java7
-rw-r--r--src/main/java/org/zwobble/couscous/backends/python/ast/PythonPassNode.java16
-rw-r--r--src/main/java/org/zwobble/couscous/backends/python/ast/PythonReturnNode.java16
-rw-r--r--src/main/java/org/zwobble/couscous/backends/python/ast/PythonStatementNode.java5
-rw-r--r--src/main/java/org/zwobble/couscous/backends/python/ast/PythonStringLiteralNode.java15
-rw-r--r--src/main/java/org/zwobble/couscous/backends/python/ast/PythonWriter.java60
-rw-r--r--src/main/java/org/zwobble/couscous/backends/python/ast/visitors/PythonNodeVisitor.java22
-rw-r--r--src/main/java/org/zwobble/couscous/values/InterpreterValues.java13
-rw-r--r--src/test/java/org/zwobble/couscous/tests/BackendTests.java49
-rw-r--r--src/test/java/org/zwobble/couscous/tests/MethodRunner.java10
-rw-r--r--src/test/java/org/zwobble/couscous/tests/backends/python/PythonCompilerTests.java70
-rw-r--r--src/test/java/org/zwobble/couscous/tests/backends/python/PythonSerializerTests.java80
-rw-r--r--src/test/java/org/zwobble/couscous/tests/interpreter/InterpreterTests.java50
-rw-r--r--src/test/java/org/zwobble/couscous/tests/util/ExtraFiles.java16
24 files changed, 798 insertions, 31 deletions
diff --git a/src/main/java/org/zwobble/couscous/ast/ClassNode.java b/src/main/java/org/zwobble/couscous/ast/ClassNode.java
index 72e57d2..3d785d2 100644
--- a/src/main/java/org/zwobble/couscous/ast/ClassNode.java
+++ b/src/main/java/org/zwobble/couscous/ast/ClassNode.java
@@ -35,4 +35,9 @@ public class ClassNode {
String name;
@Singular
List<MethodNode> methods;
+
+ public String getLocalName() {
+ // TODO Stronger notion of class names? Should look up the terminology
+ return name.substring(name.lastIndexOf(".") + 1);
+ }
}
diff --git a/src/main/java/org/zwobble/couscous/backends/python/PythonCodeGenerator.java b/src/main/java/org/zwobble/couscous/backends/python/PythonCodeGenerator.java
new file mode 100644
index 0000000..816a80d
--- /dev/null
+++ b/src/main/java/org/zwobble/couscous/backends/python/PythonCodeGenerator.java
@@ -0,0 +1,121 @@
+package org.zwobble.couscous.backends.python;
+
+import java.util.stream.Collectors;
+
+import org.zwobble.couscous.ast.Assignment;
+import org.zwobble.couscous.ast.ClassNode;
+import org.zwobble.couscous.ast.ExpressionNode;
+import org.zwobble.couscous.ast.ExpressionStatementNode;
+import org.zwobble.couscous.ast.LiteralNode;
+import org.zwobble.couscous.ast.LocalVariableDeclarationNode;
+import org.zwobble.couscous.ast.MethodCallNode;
+import org.zwobble.couscous.ast.MethodNode;
+import org.zwobble.couscous.ast.ReturnNode;
+import org.zwobble.couscous.ast.StatementNode;
+import org.zwobble.couscous.ast.StaticMethodCallNode;
+import org.zwobble.couscous.ast.TernaryConditionalNode;
+import org.zwobble.couscous.ast.VariableReferenceNode;
+import org.zwobble.couscous.ast.visitors.ExpressionNodeVisitor;
+import org.zwobble.couscous.ast.visitors.StatementNodeVisitor;
+import org.zwobble.couscous.backends.python.ast.PythonExpressionNode;
+import org.zwobble.couscous.backends.python.ast.PythonFunctionDefinitionNode;
+import org.zwobble.couscous.backends.python.ast.PythonModuleNode;
+import org.zwobble.couscous.backends.python.ast.PythonStatementNode;
+import org.zwobble.couscous.values.IntegerValue;
+import org.zwobble.couscous.values.StringValue;
+
+import static java.util.Arrays.asList;
+import static org.zwobble.couscous.backends.python.ast.PythonClassNode.pythonClass;
+import static org.zwobble.couscous.backends.python.ast.PythonFunctionDefinitionNode.pythonFunctionDefinition;
+import static org.zwobble.couscous.backends.python.ast.PythonIntegerLiteralNode.pythonIntegerLiteral;
+import static org.zwobble.couscous.backends.python.ast.PythonModuleNode.pythonModule;
+import static org.zwobble.couscous.backends.python.ast.PythonReturnNode.pythonReturn;
+import static org.zwobble.couscous.backends.python.ast.PythonStringLiteralNode.pythonStringLiteral;
+
+import lombok.val;
+
+public class PythonCodeGenerator {
+ public static PythonModuleNode generateCode(ClassNode classNode) {
+ val pythonBody = classNode.getMethods()
+ .stream()
+ .map(PythonCodeGenerator::generateFunction)
+ .collect(Collectors.toList());
+
+ val pythonClass = pythonClass(classNode.getLocalName(), pythonBody);
+ return pythonModule(asList(pythonClass));
+ }
+
+ private static PythonFunctionDefinitionNode generateFunction(MethodNode method) {
+ val pythonBody = method.getBody()
+ .stream()
+ .map(PythonCodeGenerator::generateStatement)
+ .collect(Collectors.toList());
+ return pythonFunctionDefinition(method.getName(), pythonBody);
+ }
+
+ private static PythonStatementNode generateStatement(StatementNode statement) {
+ return statement.accept(new StatementGenerator());
+ }
+
+ private static class StatementGenerator implements StatementNodeVisitor<PythonStatementNode> {
+ @Override
+ public PythonStatementNode visit(ReturnNode returnNode) {
+ return pythonReturn(generateExpression(returnNode.getValue()));
+ }
+
+ @Override
+ public PythonStatementNode visit(ExpressionStatementNode expressionStatement) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public PythonStatementNode visit(LocalVariableDeclarationNode localVariableDeclaration) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private static PythonExpressionNode generateExpression(ExpressionNode expression) {
+ return expression.accept(new ExpressionGenerator());
+ }
+
+ private static class ExpressionGenerator implements ExpressionNodeVisitor<PythonExpressionNode> {
+ @Override
+ public PythonExpressionNode visit(LiteralNode literal) {
+ // TODO: use visitor
+ if (literal.getValue() instanceof IntegerValue) {
+ return pythonIntegerLiteral(((IntegerValue)literal.getValue()).getValue());
+ } else if (literal.getValue() instanceof StringValue) {
+ return pythonStringLiteral(((StringValue)literal.getValue()).getValue());
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @Override
+ public PythonExpressionNode visit(
+ VariableReferenceNode variableReference) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public PythonExpressionNode visit(Assignment assignment) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public PythonExpressionNode visit(TernaryConditionalNode ternaryConditional) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public PythonExpressionNode visit(MethodCallNode methodCall) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public PythonExpressionNode visit(StaticMethodCallNode staticMethodCall) {
+ throw new UnsupportedOperationException();
+ }
+
+ }
+}
diff --git a/src/main/java/org/zwobble/couscous/backends/python/PythonCompiler.java b/src/main/java/org/zwobble/couscous/backends/python/PythonCompiler.java
new file mode 100644
index 0000000..b38bc73
--- /dev/null
+++ b/src/main/java/org/zwobble/couscous/backends/python/PythonCompiler.java
@@ -0,0 +1,34 @@
+package org.zwobble.couscous.backends.python;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+import org.zwobble.couscous.ast.ClassNode;
+
+import static java.util.Arrays.asList;
+import static org.zwobble.couscous.backends.python.PythonCodeGenerator.generateCode;
+import static org.zwobble.couscous.backends.python.PythonSerializer.serialize;
+
+import lombok.SneakyThrows;
+import lombok.val;
+
+public class PythonCompiler {
+ public void compile(List<ClassNode> classes, Path path) {
+ for (val classNode : classes) {
+ compileClass(classNode, pathForClass(classNode, path));
+ }
+ }
+
+ private Path pathForClass(ClassNode classNode, Path path) {
+ return path.resolve(classNode.getName().replace(".", File.separator) + ".py");
+ }
+
+ @SneakyThrows
+ private void compileClass(ClassNode classNode, Path path) {
+ Files.createDirectories(path.getParent());
+ Files.write(path, asList(serialize(generateCode(classNode))));
+
+ }
+}
diff --git a/src/main/java/org/zwobble/couscous/backends/python/PythonSerializer.java b/src/main/java/org/zwobble/couscous/backends/python/PythonSerializer.java
new file mode 100644
index 0000000..e5a13e5
--- /dev/null
+++ b/src/main/java/org/zwobble/couscous/backends/python/PythonSerializer.java
@@ -0,0 +1,107 @@
+package org.zwobble.couscous.backends.python;
+
+import org.zwobble.couscous.backends.python.ast.PythonBlock;
+import org.zwobble.couscous.backends.python.ast.PythonClassNode;
+import org.zwobble.couscous.backends.python.ast.PythonFunctionDefinitionNode;
+import org.zwobble.couscous.backends.python.ast.PythonIntegerLiteralNode;
+import org.zwobble.couscous.backends.python.ast.PythonModuleNode;
+import org.zwobble.couscous.backends.python.ast.PythonNode;
+import org.zwobble.couscous.backends.python.ast.PythonPassNode;
+import org.zwobble.couscous.backends.python.ast.PythonReturnNode;
+import org.zwobble.couscous.backends.python.ast.PythonStringLiteralNode;
+import org.zwobble.couscous.backends.python.ast.PythonWriter;
+import org.zwobble.couscous.backends.python.ast.visitors.PythonNodeVisitor;
+
+import lombok.val;
+
+public class PythonSerializer implements PythonNodeVisitor<Void> {
+ public static String serialize(PythonNode node) {
+ val writer = new PythonWriter();
+ val serializer = new PythonSerializer(writer);
+ serializer.write(node);
+ return writer.asString();
+ }
+
+ private PythonWriter writer;
+
+ private PythonSerializer(PythonWriter writer) {
+ this.writer = writer;
+ }
+
+ public void write(PythonNode node) {
+ node.accept(this);
+ }
+
+ @Override
+ public Void visit(PythonIntegerLiteralNode integerLiteral) {
+ writer.writeInteger(integerLiteral.getValue());
+ return null;
+ }
+
+ @Override
+ public Void visit(PythonStringLiteralNode stringLiteral) {
+ writer.writeStringLiteral(stringLiteral.getValue());
+ return null;
+ }
+
+ @Override
+ public Void visit(PythonReturnNode pythonReturn) {
+ writer.startStatement();
+ writer.writeKeyword("return");
+ writer.writeSpace();
+ write(pythonReturn.getValue());
+ writer.endStatement();
+ return null;
+ }
+
+ @Override
+ public Void visit(PythonPassNode pass) {
+ writer.startStatement();
+ writer.writeKeyword("pass");
+ writer.endStatement();
+ return null;
+ }
+
+ @Override
+ public Void visit(PythonClassNode pythonClass) {
+ writer.startStatement();
+ writer.writeKeyword("class");
+ writer.writeSpace();
+ writer.writeIdentifier(pythonClass.getName());
+ writer.writeSymbol("(");
+ writer.writeIdentifier("object");
+ writer.writeSymbol(")");
+ writeBlock(pythonClass.getBody());
+ writer.endStatement();
+ return null;
+ }
+
+ @Override
+ public Void visit(PythonFunctionDefinitionNode functionDefinition) {
+ writer.startStatement();
+ writer.writeKeyword("def");
+ writer.writeSpace();
+ writer.writeIdentifier(functionDefinition.getName());
+ writer.writeSymbol("(");
+ writer.writeSymbol(")");
+ writeBlock(functionDefinition.getBody());
+ writer.endStatement();
+ return null;
+ }
+
+ @Override
+ public Void visit(PythonModuleNode module) {
+ for (val statement : module.getStatements()) {
+ write(statement);
+ }
+ return null;
+ }
+
+ private void writeBlock(PythonBlock body) {
+ writer.startBlock();
+ for (val statement : body) {
+ write(statement);
+ }
+ writer.endBlock();
+ }
+}
diff --git a/src/main/java/org/zwobble/couscous/backends/python/ast/PythonBlock.java b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonBlock.java
new file mode 100644
index 0000000..8bc7132
--- /dev/null
+++ b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonBlock.java
@@ -0,0 +1,25 @@
+package org.zwobble.couscous.backends.python.ast;
+
+import java.util.Iterator;
+import java.util.List;
+
+import com.google.common.collect.ImmutableList;
+
+import static java.util.Arrays.asList;
+
+public class PythonBlock implements Iterable<PythonStatementNode> {
+ private final List<PythonStatementNode> statements;
+
+ public PythonBlock(List<? extends PythonStatementNode> statements) {
+ if (statements.isEmpty()) {
+ this.statements = asList(PythonPassNode.PASS);
+ } else {
+ this.statements = ImmutableList.copyOf(statements);
+ }
+ }
+
+ @Override
+ public Iterator<PythonStatementNode> iterator() {
+ return statements.iterator();
+ }
+}
diff --git a/src/main/java/org/zwobble/couscous/backends/python/ast/PythonClassNode.java b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonClassNode.java
new file mode 100644
index 0000000..958a21a
--- /dev/null
+++ b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonClassNode.java
@@ -0,0 +1,48 @@
+package org.zwobble.couscous.backends.python.ast;
+
+import java.util.List;
+
+import org.zwobble.couscous.backends.python.ast.visitors.PythonNodeVisitor;
+
+import com.google.common.collect.ImmutableList;
+
+import lombok.Value;
+
+@Value(staticConstructor="pythonClass")
+public class PythonClassNode implements PythonStatementNode {
+ public static Builder builder(String name) {
+ return new Builder(name);
+ }
+
+ public static class Builder {
+ private final String name;
+ private final ImmutableList.Builder<PythonStatementNode> statements;
+
+ public Builder(String name) {
+ this.name = name;
+ this.statements = ImmutableList.builder();
+ }
+
+ public PythonClassNode build() {
+ return new PythonClassNode(name, new PythonBlock(statements.build()));
+ }
+
+ public Builder statement(PythonStatementNode statement) {
+ statements.add(statement);
+ return this;
+ }
+ }
+
+ public static PythonClassNode pythonClass(String name, List<? extends PythonStatementNode> body) {
+ return new PythonClassNode(name, new PythonBlock(body));
+ }
+
+ String name;
+ PythonBlock body;
+
+ @Override
+ public <T> T accept(PythonNodeVisitor<T> visitor) {
+ return visitor.visit(this);
+ }
+
+}
diff --git a/src/main/java/org/zwobble/couscous/backends/python/ast/PythonExpressionNode.java b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonExpressionNode.java
new file mode 100644
index 0000000..9c8a259
--- /dev/null
+++ b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonExpressionNode.java
@@ -0,0 +1,5 @@
+package org.zwobble.couscous.backends.python.ast;
+
+public interface PythonExpressionNode extends PythonNode {
+
+}
diff --git a/src/main/java/org/zwobble/couscous/backends/python/ast/PythonFunctionDefinitionNode.java b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonFunctionDefinitionNode.java
new file mode 100644
index 0000000..6d66df8
--- /dev/null
+++ b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonFunctionDefinitionNode.java
@@ -0,0 +1,22 @@
+package org.zwobble.couscous.backends.python.ast;
+
+import java.util.List;
+
+import org.zwobble.couscous.backends.python.ast.visitors.PythonNodeVisitor;
+
+import lombok.Value;
+
+@Value(staticConstructor="pythonFunctionDefinition")
+public class PythonFunctionDefinitionNode implements PythonStatementNode {
+ public static PythonFunctionDefinitionNode pythonFunctionDefinition(String name, List<PythonStatementNode> body) {
+ return pythonFunctionDefinition(name, new PythonBlock(body));
+ }
+
+ String name;
+ PythonBlock body;
+
+ @Override
+ public <T> T accept(PythonNodeVisitor<T> visitor) {
+ return visitor.visit(this);
+ }
+}
diff --git a/src/main/java/org/zwobble/couscous/backends/python/ast/PythonIntegerLiteralNode.java b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonIntegerLiteralNode.java
new file mode 100644
index 0000000..d8a9358
--- /dev/null
+++ b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonIntegerLiteralNode.java
@@ -0,0 +1,15 @@
+package org.zwobble.couscous.backends.python.ast;
+
+import org.zwobble.couscous.backends.python.ast.visitors.PythonNodeVisitor;
+
+import lombok.Value;
+
+@Value(staticConstructor="pythonIntegerLiteral")
+public class PythonIntegerLiteralNode implements PythonExpressionNode {
+ int value;
+
+ @Override
+ public <T> T accept(PythonNodeVisitor<T> visitor) {
+ return visitor.visit(this);
+ }
+}
diff --git a/src/main/java/org/zwobble/couscous/backends/python/ast/PythonModuleNode.java b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonModuleNode.java
new file mode 100644
index 0000000..92d63a8
--- /dev/null
+++ b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonModuleNode.java
@@ -0,0 +1,18 @@
+package org.zwobble.couscous.backends.python.ast;
+
+import java.util.List;
+
+import org.zwobble.couscous.backends.python.ast.visitors.PythonNodeVisitor;
+
+import lombok.Value;
+
+@Value(staticConstructor="pythonModule")
+public class PythonModuleNode implements PythonNode {
+ List<PythonStatementNode> statements;
+
+ @Override
+ public <T> T accept(PythonNodeVisitor<T> visitor) {
+ return visitor.visit(this);
+ }
+
+}
diff --git a/src/main/java/org/zwobble/couscous/backends/python/ast/PythonNode.java b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonNode.java
new file mode 100644
index 0000000..cdda65d
--- /dev/null
+++ b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonNode.java
@@ -0,0 +1,7 @@
+package org.zwobble.couscous.backends.python.ast;
+
+import org.zwobble.couscous.backends.python.ast.visitors.PythonNodeVisitor;
+
+public interface PythonNode {
+ <T> T accept(PythonNodeVisitor<T> visitor);
+}
diff --git a/src/main/java/org/zwobble/couscous/backends/python/ast/PythonPassNode.java b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonPassNode.java
new file mode 100644
index 0000000..36d6695
--- /dev/null
+++ b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonPassNode.java
@@ -0,0 +1,16 @@
+package org.zwobble.couscous.backends.python.ast;
+
+import org.zwobble.couscous.backends.python.ast.visitors.PythonNodeVisitor;
+
+public class PythonPassNode implements PythonStatementNode {
+ public static final PythonPassNode PASS = new PythonPassNode();
+
+ private PythonPassNode() {
+ // TODO Auto-generated constructor stub
+ }
+
+ @Override
+ public <T> T accept(PythonNodeVisitor<T> visitor) {
+ return visitor.visit(this);
+ }
+}
diff --git a/src/main/java/org/zwobble/couscous/backends/python/ast/PythonReturnNode.java b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonReturnNode.java
new file mode 100644
index 0000000..b204b4d
--- /dev/null
+++ b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonReturnNode.java
@@ -0,0 +1,16 @@
+package org.zwobble.couscous.backends.python.ast;
+
+import org.zwobble.couscous.backends.python.ast.visitors.PythonNodeVisitor;
+
+import lombok.Value;
+
+@Value(staticConstructor="pythonReturn")
+public class PythonReturnNode implements PythonStatementNode {
+ PythonExpressionNode value;
+
+ @Override
+ public <T> T accept(PythonNodeVisitor<T> visitor) {
+ return visitor.visit(this);
+ }
+
+}
diff --git a/src/main/java/org/zwobble/couscous/backends/python/ast/PythonStatementNode.java b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonStatementNode.java
new file mode 100644
index 0000000..c27a7dd
--- /dev/null
+++ b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonStatementNode.java
@@ -0,0 +1,5 @@
+package org.zwobble.couscous.backends.python.ast;
+
+public interface PythonStatementNode extends PythonNode {
+
+}
diff --git a/src/main/java/org/zwobble/couscous/backends/python/ast/PythonStringLiteralNode.java b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonStringLiteralNode.java
new file mode 100644
index 0000000..82d91db
--- /dev/null
+++ b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonStringLiteralNode.java
@@ -0,0 +1,15 @@
+package org.zwobble.couscous.backends.python.ast;
+
+import org.zwobble.couscous.backends.python.ast.visitors.PythonNodeVisitor;
+
+import lombok.Value;
+
+@Value(staticConstructor="pythonStringLiteral")
+public class PythonStringLiteralNode implements PythonExpressionNode {
+ String value;
+
+ @Override
+ public <T> T accept(PythonNodeVisitor<T> visitor) {
+ return visitor.visit(this);
+ }
+}
diff --git a/src/main/java/org/zwobble/couscous/backends/python/ast/PythonWriter.java b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonWriter.java
new file mode 100644
index 0000000..4ab04c6
--- /dev/null
+++ b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonWriter.java
@@ -0,0 +1,60 @@
+package org.zwobble.couscous.backends.python.ast;
+
+import com.google.common.base.Strings;
+
+public class PythonWriter {
+ private static final int SPACES_PER_INDENT = 4;
+ private final StringBuilder builder = new StringBuilder();
+ private int depth = 0;
+
+ public String asString() {
+ return builder.toString();
+ }
+
+ public void writeInteger(int value) {
+ builder.append(value);
+ }
+
+ public void writeStringLiteral(String value) {
+ builder.append("\"");
+ builder.append(value);
+ builder.append("\"");
+ }
+
+ public void writeKeyword(String keyword) {
+ builder.append(keyword);
+ }
+
+ public void writeSpace() {
+ builder.append(" ");
+ }
+
+ public void writeIdentifier(String name) {
+ builder.append(name);
+ }
+
+ public void writeSymbol(String symbol) {
+ builder.append(symbol);
+ }
+
+ public void startBlock() {
+ depth++;
+ writeSymbol(":");
+ builder.append("\n");
+ }
+
+ public void endBlock() {
+ depth--;
+ }
+
+ public void startStatement() {
+ builder.append(Strings.repeat(" ", depth * SPACES_PER_INDENT));
+ }
+
+ public void endStatement() {
+ if (builder.charAt(builder.length() - 1) != '\n') {
+ builder.append("\n");
+ }
+ }
+
+}
diff --git a/src/main/java/org/zwobble/couscous/backends/python/ast/visitors/PythonNodeVisitor.java b/src/main/java/org/zwobble/couscous/backends/python/ast/visitors/PythonNodeVisitor.java
new file mode 100644
index 0000000..0210df6
--- /dev/null
+++ b/src/main/java/org/zwobble/couscous/backends/python/ast/visitors/PythonNodeVisitor.java
@@ -0,0 +1,22 @@
+package org.zwobble.couscous.backends.python.ast.visitors;
+
+import org.zwobble.couscous.backends.python.ast.PythonClassNode;
+import org.zwobble.couscous.backends.python.ast.PythonFunctionDefinitionNode;
+import org.zwobble.couscous.backends.python.ast.PythonIntegerLiteralNode;
+import org.zwobble.couscous.backends.python.ast.PythonModuleNode;
+import org.zwobble.couscous.backends.python.ast.PythonPassNode;
+import org.zwobble.couscous.backends.python.ast.PythonReturnNode;
+import org.zwobble.couscous.backends.python.ast.PythonStringLiteralNode;
+
+public interface PythonNodeVisitor<T> {
+ T visit(PythonIntegerLiteralNode integerLiteral);
+ T visit(PythonStringLiteralNode stringLiteral);
+
+ T visit(PythonReturnNode pythonReturn);
+ T visit(PythonPassNode pass);
+ T visit(PythonClassNode pythonClass);
+ T visit(PythonFunctionDefinitionNode functionDefinition);
+
+ T visit(PythonModuleNode module);
+
+}
diff --git a/src/main/java/org/zwobble/couscous/values/InterpreterValues.java b/src/main/java/org/zwobble/couscous/values/InterpreterValues.java
new file mode 100644
index 0000000..6b220d0
--- /dev/null
+++ b/src/main/java/org/zwobble/couscous/values/InterpreterValues.java
@@ -0,0 +1,13 @@
+package org.zwobble.couscous.values;
+
+public class InterpreterValues {
+ public static final UnitValue UNIT = UnitValue.UNIT;
+
+ public static InterpreterValue value(int value) {
+ return new IntegerValue(value);
+ }
+
+ public static InterpreterValue value(String value) {
+ return new StringValue(value);
+ }
+}
diff --git a/src/test/java/org/zwobble/couscous/tests/BackendTests.java b/src/test/java/org/zwobble/couscous/tests/BackendTests.java
new file mode 100644
index 0000000..86818af
--- /dev/null
+++ b/src/test/java/org/zwobble/couscous/tests/BackendTests.java
@@ -0,0 +1,49 @@
+package org.zwobble.couscous.tests;
+
+import org.junit.Test;
+import org.zwobble.couscous.ast.ClassNode;
+import org.zwobble.couscous.ast.MethodNode;
+import org.zwobble.couscous.ast.ReturnNode;
+import org.zwobble.couscous.values.InterpreterValue;
+import org.zwobble.couscous.values.StringValue;
+
+import static java.util.Arrays.asList;
+import static org.junit.Assert.assertEquals;
+import static org.zwobble.couscous.ast.LiteralNode.literal;
+import static org.zwobble.couscous.ast.MethodNode.staticMethod;
+import static org.zwobble.couscous.values.UnitValue.UNIT;
+
+import lombok.val;
+
+public abstract class BackendTests {
+ @Test
+ public void methodWithNoStatementsReturnsUnit() {
+ val method = staticMethod("hello");
+ val result = runMethod(method);
+
+ assertEquals(UNIT, result);
+ }
+
+ @Test
+ public void canReturnLiteralValue() {
+ val method = staticMethod("hello")
+ .statement(new ReturnNode(literal("hello, world!")));
+ val result = runMethod(method);
+
+ assertEquals(new StringValue("hello, world!"), result);
+ }
+
+ protected InterpreterValue runMethod(MethodNode.MethodNodeBuilder methodBuilder, InterpreterValue... arguments) {
+ val method = methodBuilder.build();
+ val className = "com.example.Program";
+ val classNode = ClassNode.builder(className)
+ .method(method)
+ .build();
+
+ val runner = buildMethodRunner();
+ return runner.runMethod(classNode, method.getName(), asList(arguments));
+ }
+
+ protected abstract MethodRunner buildMethodRunner();
+
+}
diff --git a/src/test/java/org/zwobble/couscous/tests/MethodRunner.java b/src/test/java/org/zwobble/couscous/tests/MethodRunner.java
new file mode 100644
index 0000000..29c15fe
--- /dev/null
+++ b/src/test/java/org/zwobble/couscous/tests/MethodRunner.java
@@ -0,0 +1,10 @@
+package org.zwobble.couscous.tests;
+
+import java.util.List;
+
+import org.zwobble.couscous.ast.ClassNode;
+import org.zwobble.couscous.values.InterpreterValue;
+
+public interface MethodRunner {
+ InterpreterValue runMethod(ClassNode classNode, String methodName, List<InterpreterValue> arguments);
+}
diff --git a/src/test/java/org/zwobble/couscous/tests/backends/python/PythonCompilerTests.java b/src/test/java/org/zwobble/couscous/tests/backends/python/PythonCompilerTests.java
new file mode 100644
index 0000000..7419aca
--- /dev/null
+++ b/src/test/java/org/zwobble/couscous/tests/backends/python/PythonCompilerTests.java
@@ -0,0 +1,70 @@
+package org.zwobble.couscous.tests.backends.python;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.file.Files;
+import java.util.List;
+
+import org.zwobble.couscous.ast.ClassNode;
+import org.zwobble.couscous.backends.python.PythonCompiler;
+import org.zwobble.couscous.tests.BackendTests;
+import org.zwobble.couscous.tests.MethodRunner;
+import org.zwobble.couscous.values.InterpreterValue;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.CharStreams;
+
+import static java.lang.Integer.parseInt;
+import static java.util.Arrays.asList;
+import static org.zwobble.couscous.tests.util.ExtraFiles.deleteRecursively;
+import static org.zwobble.couscous.values.InterpreterValues.UNIT;
+import static org.zwobble.couscous.values.InterpreterValues.value;
+
+import lombok.SneakyThrows;
+import lombok.val;
+
+public class PythonCompilerTests extends BackendTests {
+ @Override
+ protected MethodRunner buildMethodRunner() {
+ return new MethodRunner() {
+ @Override
+ @SneakyThrows
+ public InterpreterValue runMethod(
+ ClassNode classNode,
+ String methodName,
+ List<InterpreterValue> arguments) {
+ val compiler = new PythonCompiler();
+ val directoryPath = Files.createTempDirectory(null);
+ try {
+ compiler.compile(asList(classNode), directoryPath);
+
+ val program = "from " + classNode.getName() + " import " + classNode.getLocalName() +
+ ";print(repr(" + classNode.getLocalName() + "." + methodName + "()))";
+
+ val process = new ProcessBuilder("python3.4", "-c", program)
+ .directory(directoryPath.toFile())
+ .start();
+ val exitCode = process.waitFor();
+ val output = readString(process.getInputStream()).trim();
+ val stderrOutput = readString(process.getErrorStream());
+ if (exitCode != 0) {
+ throw new RuntimeException("stderr was: " + stderrOutput);
+ } else if (output.equals("None")) {
+ return UNIT;
+ } else if (output.startsWith("'")) {
+ return value(output.substring(1, output.length() - 1));
+ } else {
+ return value(parseInt(output));
+ }
+ } finally {
+ deleteRecursively(directoryPath.toFile());
+ }
+ }
+
+ private String readString(final InputStream stream) throws IOException {
+ return CharStreams.toString(new InputStreamReader(stream, Charsets.UTF_8));
+ }
+ };
+ }
+}
diff --git a/src/test/java/org/zwobble/couscous/tests/backends/python/PythonSerializerTests.java b/src/test/java/org/zwobble/couscous/tests/backends/python/PythonSerializerTests.java
new file mode 100644
index 0000000..4f9b63b
--- /dev/null
+++ b/src/test/java/org/zwobble/couscous/tests/backends/python/PythonSerializerTests.java
@@ -0,0 +1,80 @@
+package org.zwobble.couscous.tests.backends.python;
+
+import org.junit.Test;
+import org.zwobble.couscous.backends.python.ast.PythonClassNode;
+
+import static java.util.Arrays.asList;
+import static org.junit.Assert.assertEquals;
+import static org.zwobble.couscous.backends.python.PythonSerializer.serialize;
+import static org.zwobble.couscous.backends.python.ast.PythonFunctionDefinitionNode.pythonFunctionDefinition;
+import static org.zwobble.couscous.backends.python.ast.PythonIntegerLiteralNode.pythonIntegerLiteral;
+import static org.zwobble.couscous.backends.python.ast.PythonModuleNode.pythonModule;
+import static org.zwobble.couscous.backends.python.ast.PythonPassNode.PASS;
+import static org.zwobble.couscous.backends.python.ast.PythonReturnNode.pythonReturn;
+import static org.zwobble.couscous.backends.python.ast.PythonStringLiteralNode.pythonStringLiteral;
+
+import lombok.val;
+
+public class PythonSerializerTests {
+ @Test
+ public void integersAreNotBoxed() {
+ val output = serialize(pythonIntegerLiteral(42));
+ assertEquals("42", output);
+ }
+
+ @Test
+ public void stringsAreNotBoxed() {
+ // TODO: escaping
+ val output = serialize(pythonStringLiteral("blah"));
+ assertEquals("\"blah\"", output);
+ }
+
+ @Test
+ public void returnKeywordIsUsedForReturns() {
+ val output = serialize(pythonReturn(pythonIntegerLiteral(42)));
+ assertEquals("return 42\n", output);
+ }
+
+ @Test
+ public void passStatementIsSerializedAsPassKeyword() {
+ val output = serialize(PASS);
+ assertEquals("pass\n", output);
+ }
+
+ @Test
+ public void emptyFunctionHasPassStatement() {
+ val output = serialize(pythonFunctionDefinition("empty", asList()));
+ assertEquals("def empty():\n pass\n", output);
+ }
+
+ @Test
+ public void emptyClassHasPassStatement() {
+ val classNode = PythonClassNode.builder("Empty")
+ .build();
+ val output = serialize(classNode);
+ assertEquals("class Empty(object):\n pass\n", output);
+ }
+
+ @Test
+ public void moduleIsSerializedStatements() {
+ val classNode = pythonModule(asList(PASS));
+ val output = serialize(classNode);
+ assertEquals("pass\n", output);
+ }
+
+ @Test
+ public void bodiesOfBlocksAreIndented() {
+ val classNode = PythonClassNode.builder("Foo")
+ .statement(pythonFunctionDefinition("one", asList()))
+ .statement(pythonFunctionDefinition("two", asList()))
+ .build();
+ val output = serialize(classNode);
+ val expectedOutput =
+ "class Foo(object):\n" +
+ " def one():\n" +
+ " pass\n" +
+ " def two():\n" +
+ " pass\n";
+ assertEquals(expectedOutput, output);
+ }
+}
diff --git a/src/test/java/org/zwobble/couscous/tests/interpreter/InterpreterTests.java b/src/test/java/org/zwobble/couscous/tests/interpreter/InterpreterTests.java
index d0c8976..98271f7 100644
--- a/src/test/java/org/zwobble/couscous/tests/interpreter/InterpreterTests.java
+++ b/src/test/java/org/zwobble/couscous/tests/interpreter/InterpreterTests.java
@@ -1,18 +1,21 @@
package org.zwobble.couscous.tests.interpreter;
+import java.util.List;
+
import org.junit.Test;
import org.zwobble.couscous.MapBackedProject;
import org.zwobble.couscous.ast.Assignment;
import org.zwobble.couscous.ast.ClassNode;
import org.zwobble.couscous.ast.ExpressionStatementNode;
import org.zwobble.couscous.ast.LiteralNode;
-import org.zwobble.couscous.ast.MethodNode;
import org.zwobble.couscous.ast.ReturnNode;
import org.zwobble.couscous.interpreter.Interpreter;
import org.zwobble.couscous.interpreter.UnboundVariable;
import org.zwobble.couscous.interpreter.UnexpectedValueType;
import org.zwobble.couscous.interpreter.VariableNotInScope;
import org.zwobble.couscous.interpreter.WrongNumberOfArguments;
+import org.zwobble.couscous.tests.BackendTests;
+import org.zwobble.couscous.tests.MethodRunner;
import org.zwobble.couscous.values.ConcreteType;
import org.zwobble.couscous.values.IntegerValue;
import org.zwobble.couscous.values.InterpreterValue;
@@ -20,7 +23,6 @@ import org.zwobble.couscous.values.StringValue;
import com.google.common.collect.ImmutableMap;
-import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
import static org.zwobble.couscous.ast.FormalArgumentNode.formalArg;
import static org.zwobble.couscous.ast.LiteralNode.literal;
@@ -29,28 +31,10 @@ import static org.zwobble.couscous.ast.MethodNode.staticMethod;
import static org.zwobble.couscous.ast.VariableDeclaration.var;
import static org.zwobble.couscous.ast.VariableReferenceNode.reference;
import static org.zwobble.couscous.tests.util.ExtraAsserts.assertThrows;
-import static org.zwobble.couscous.values.UnitValue.UNIT;
import lombok.val;
-public class InterpreterTests {
- @Test
- public void methodWithNoStatementsReturnsUnit() {
- val method = staticMethod("hello");
- val result = runMethod(method);
-
- assertEquals(UNIT, result);
- }
-
- @Test
- public void canReturnLiteralValue() {
- val method = staticMethod("hello")
- .statement(new ReturnNode(LiteralNode.literal("hello, world!")));
- val result = runMethod(method);
-
- assertEquals(new StringValue("hello, world!"), result);
- }
-
+public class InterpreterTests extends BackendTests {
@Test
public void canPassValueToMethod() {
val arg = formalArg(var(42, "x", StringValue.REF));
@@ -162,15 +146,19 @@ public class InterpreterTests {
assertEquals(new UnboundVariable(42), exception);
}
- private InterpreterValue runMethod(MethodNode.MethodNodeBuilder methodBuilder, InterpreterValue... arguments) {
- val method = methodBuilder.build();
- val className = "com.example.Program";
- val classNode = ClassNode.builder(className)
- .method(method)
- .build();
- val interpreter = new Interpreter(new MapBackedProject(ImmutableMap.of(
- className, ConcreteType.fromNode(classNode))));
-
- return interpreter.run(className, method.getName(), asList(arguments));
+ @Override
+ protected MethodRunner buildMethodRunner() {
+ return new MethodRunner() {
+ @Override
+ public InterpreterValue runMethod(
+ ClassNode classNode,
+ String methodName,
+ List<InterpreterValue> arguments) {
+ val interpreter = new Interpreter(new MapBackedProject(ImmutableMap.of(
+ classNode.getName(), ConcreteType.fromNode(classNode))));
+
+ return interpreter.run(classNode.getName(), methodName, arguments);
+ }
+ };
}
}
diff --git a/src/test/java/org/zwobble/couscous/tests/util/ExtraFiles.java b/src/test/java/org/zwobble/couscous/tests/util/ExtraFiles.java
new file mode 100644
index 0000000..032e22c
--- /dev/null
+++ b/src/test/java/org/zwobble/couscous/tests/util/ExtraFiles.java
@@ -0,0 +1,16 @@
+package org.zwobble.couscous.tests.util;
+
+import java.io.File;
+
+import lombok.val;
+
+public class ExtraFiles {
+ public static void deleteRecursively(File file) {
+ if (file.isDirectory()) {
+ for (val child : file.listFiles()) {
+ deleteRecursively(child);
+ }
+ }
+ file.delete();
+ }
+}