aboutsummaryrefslogtreecommitdiff
path: root/src/test/java
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2015-10-05 11:04:17 +0100
committerMichael Williamson <mike@zwobble.org>2015-10-05 11:04:17 +0100
commit96b1e6f776ae8ce8910c44a3bbec0b489fd10c88 (patch)
tree86658222ef741b1620c1635d3fd98d99452a387b /src/test/java
parentcdf51cdb9b3f8cef5896315496f348d9f7220903 (diff)
Add the beginnings of a Python backend
Diffstat (limited to 'src/test/java')
-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
6 files changed, 244 insertions, 31 deletions
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();
+ }
+}