aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/java/org/zwobble/couscous/backends/python/PythonCodeGenerator.java9
-rw-r--r--src/main/java/org/zwobble/couscous/backends/python/PythonSerializer.java22
-rw-r--r--src/main/java/org/zwobble/couscous/backends/python/ast/PythonConditionalExpressionNode.java17
-rw-r--r--src/main/java/org/zwobble/couscous/backends/python/ast/visitors/PythonNodeVisitor.java2
-rw-r--r--src/test/java/org/zwobble/couscous/tests/BackendEvalTests.java28
-rw-r--r--src/test/java/org/zwobble/couscous/tests/backends/python/PythonCompilerEvalTests.java27
-rw-r--r--src/test/java/org/zwobble/couscous/tests/backends/python/PythonCompilerMethodTests.java75
-rw-r--r--src/test/java/org/zwobble/couscous/tests/backends/python/PythonMethodRunner.java78
-rw-r--r--src/test/java/org/zwobble/couscous/tests/backends/python/PythonSerializerTests.java10
-rw-r--r--src/test/java/org/zwobble/couscous/tests/interpreter/EvaluatorTests.java24
10 files changed, 201 insertions, 91 deletions
diff --git a/src/main/java/org/zwobble/couscous/backends/python/PythonCodeGenerator.java b/src/main/java/org/zwobble/couscous/backends/python/PythonCodeGenerator.java
index b1947a8..0db2be6 100644
--- a/src/main/java/org/zwobble/couscous/backends/python/PythonCodeGenerator.java
+++ b/src/main/java/org/zwobble/couscous/backends/python/PythonCodeGenerator.java
@@ -32,7 +32,9 @@ import org.zwobble.couscous.values.UnitValue;
import static java.util.Arrays.asList;
import static org.zwobble.couscous.backends.python.ast.PythonAssignmentNode.pythonAssignment;
+import static org.zwobble.couscous.backends.python.ast.PythonBooleanLiteralNode.pythonBooleanLiteral;
import static org.zwobble.couscous.backends.python.ast.PythonClassNode.pythonClass;
+import static org.zwobble.couscous.backends.python.ast.PythonConditionalExpressionNode.pythonConditionalExpression;
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;
@@ -67,7 +69,7 @@ public class PythonCodeGenerator {
@Override
public PythonExpressionNode visit(BooleanValue value) {
- throw new UnsupportedOperationException();
+ return pythonBooleanLiteral(value.getValue());
}
@Override
@@ -144,7 +146,10 @@ public class PythonCodeGenerator {
@Override
public PythonExpressionNode visit(TernaryConditionalNode ternaryConditional) {
- throw new UnsupportedOperationException();
+ return pythonConditionalExpression(
+ generateExpression(ternaryConditional.getCondition()),
+ generateExpression(ternaryConditional.getIfTrue()),
+ generateExpression(ternaryConditional.getIfFalse()));
}
@Override
diff --git a/src/main/java/org/zwobble/couscous/backends/python/PythonSerializer.java b/src/main/java/org/zwobble/couscous/backends/python/PythonSerializer.java
index bc5fb19..728b71a 100644
--- a/src/main/java/org/zwobble/couscous/backends/python/PythonSerializer.java
+++ b/src/main/java/org/zwobble/couscous/backends/python/PythonSerializer.java
@@ -4,6 +4,8 @@ import org.zwobble.couscous.backends.python.ast.PythonAssignmentNode;
import org.zwobble.couscous.backends.python.ast.PythonBlock;
import org.zwobble.couscous.backends.python.ast.PythonBooleanLiteralNode;
import org.zwobble.couscous.backends.python.ast.PythonClassNode;
+import org.zwobble.couscous.backends.python.ast.PythonConditionalExpressionNode;
+import org.zwobble.couscous.backends.python.ast.PythonExpressionNode;
import org.zwobble.couscous.backends.python.ast.PythonFunctionDefinitionNode;
import org.zwobble.couscous.backends.python.ast.PythonIntegerLiteralNode;
import org.zwobble.couscous.backends.python.ast.PythonModuleNode;
@@ -62,6 +64,20 @@ public class PythonSerializer implements PythonNodeVisitor<Void> {
}
@Override
+ public Void visit(PythonConditionalExpressionNode conditional) {
+ writeParenthesised(conditional.getTrueValue());
+ writer.writeSpace();
+ writer.writeKeyword("if");
+ writer.writeSpace();
+ writeParenthesised(conditional.getCondition());
+ writer.writeSpace();
+ writer.writeKeyword("else");
+ writer.writeSpace();
+ writeParenthesised(conditional.getFalseValue());
+ return null;
+ }
+
+ @Override
public Void visit(PythonReturnNode pythonReturn) {
writer.startStatement();
writer.writeKeyword("return");
@@ -119,6 +135,12 @@ public class PythonSerializer implements PythonNodeVisitor<Void> {
return null;
}
+ private void writeParenthesised(PythonExpressionNode expression) {
+ writer.writeSymbol("(");
+ write(expression);
+ writer.writeSymbol(")");
+ }
+
private void writeArgumentNames(PythonFunctionDefinitionNode functionDefinition) {
if (!functionDefinition.getArgumentNames().isEmpty()) {
writer.writeIdentifier(functionDefinition.getArgumentNames().get(0));
diff --git a/src/main/java/org/zwobble/couscous/backends/python/ast/PythonConditionalExpressionNode.java b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonConditionalExpressionNode.java
new file mode 100644
index 0000000..9fd406c
--- /dev/null
+++ b/src/main/java/org/zwobble/couscous/backends/python/ast/PythonConditionalExpressionNode.java
@@ -0,0 +1,17 @@
+package org.zwobble.couscous.backends.python.ast;
+
+import org.zwobble.couscous.backends.python.ast.visitors.PythonNodeVisitor;
+
+import lombok.Value;
+
+@Value(staticConstructor="pythonConditionalExpression")
+public class PythonConditionalExpressionNode implements PythonExpressionNode {
+ PythonExpressionNode condition;
+ PythonExpressionNode trueValue;
+ PythonExpressionNode falseValue;
+
+ @Override
+ public <T> T accept(PythonNodeVisitor<T> visitor) {
+ return visitor.visit(this);
+ }
+}
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
index f5cd04f..1f5b1a2 100644
--- 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
@@ -3,6 +3,7 @@ package org.zwobble.couscous.backends.python.ast.visitors;
import org.zwobble.couscous.backends.python.ast.PythonAssignmentNode;
import org.zwobble.couscous.backends.python.ast.PythonBooleanLiteralNode;
import org.zwobble.couscous.backends.python.ast.PythonClassNode;
+import org.zwobble.couscous.backends.python.ast.PythonConditionalExpressionNode;
import org.zwobble.couscous.backends.python.ast.PythonFunctionDefinitionNode;
import org.zwobble.couscous.backends.python.ast.PythonIntegerLiteralNode;
import org.zwobble.couscous.backends.python.ast.PythonModuleNode;
@@ -16,6 +17,7 @@ public interface PythonNodeVisitor<T> {
T visit(PythonStringLiteralNode stringLiteral);
T visit(PythonBooleanLiteralNode booleanLiteral);
T visit(PythonVariableReferenceNode reference);
+ T visit(PythonConditionalExpressionNode conditional);
T visit(PythonReturnNode pythonReturn);
T visit(PythonPassNode pass);
diff --git a/src/test/java/org/zwobble/couscous/tests/BackendEvalTests.java b/src/test/java/org/zwobble/couscous/tests/BackendEvalTests.java
new file mode 100644
index 0000000..c4a3538
--- /dev/null
+++ b/src/test/java/org/zwobble/couscous/tests/BackendEvalTests.java
@@ -0,0 +1,28 @@
+package org.zwobble.couscous.tests;
+
+import org.junit.Test;
+import org.zwobble.couscous.ast.ExpressionNode;
+import org.zwobble.couscous.ast.TernaryConditionalNode;
+import org.zwobble.couscous.values.PrimitiveValue;
+
+import static org.junit.Assert.assertEquals;
+import static org.zwobble.couscous.ast.LiteralNode.literal;
+import static org.zwobble.couscous.values.PrimitiveValues.value;
+
+import lombok.val;
+
+public abstract class BackendEvalTests {
+ @Test
+ public void whenConditionIsTrueThenValueOfConditionalTernaryIsTrueBranch() {
+ val result = evalExpression(new TernaryConditionalNode(literal(true), literal("T"), literal("F")));
+ assertEquals(value("T"), result);
+ }
+
+ @Test
+ public void whenConditionIsFalseThenValueOfConditionalTernaryIsFalseBranch() {
+ val result = evalExpression(new TernaryConditionalNode(literal(false), literal("T"), literal("F")));
+ assertEquals(value("F"), result);
+ }
+
+ protected abstract PrimitiveValue evalExpression(ExpressionNode expression);
+}
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
new file mode 100644
index 0000000..6f19eb3
--- /dev/null
+++ b/src/test/java/org/zwobble/couscous/tests/backends/python/PythonCompilerEvalTests.java
@@ -0,0 +1,27 @@
+package org.zwobble.couscous.tests.backends.python;
+
+import org.zwobble.couscous.ast.ClassNode;
+import org.zwobble.couscous.ast.ExpressionNode;
+import org.zwobble.couscous.ast.MethodNode;
+import org.zwobble.couscous.ast.ReturnNode;
+import org.zwobble.couscous.tests.BackendEvalTests;
+import org.zwobble.couscous.values.PrimitiveValue;
+
+import com.google.common.collect.ImmutableList;
+
+import lombok.val;
+
+public class PythonCompilerEvalTests extends BackendEvalTests {
+ @Override
+ protected PrimitiveValue evalExpression(ExpressionNode expression) {
+ val runner = new PythonMethodRunner();
+ return runner.runMethod(
+ ClassNode.builder("Program")
+ .method(MethodNode.staticMethod("run")
+ .statement(new ReturnNode(expression))
+ .build())
+ .build(),
+ "run",
+ ImmutableList.of());
+ }
+}
diff --git a/src/test/java/org/zwobble/couscous/tests/backends/python/PythonCompilerMethodTests.java b/src/test/java/org/zwobble/couscous/tests/backends/python/PythonCompilerMethodTests.java
index 93c283a..6666ac4 100644
--- a/src/test/java/org/zwobble/couscous/tests/backends/python/PythonCompilerMethodTests.java
+++ b/src/test/java/org/zwobble/couscous/tests/backends/python/PythonCompilerMethodTests.java
@@ -1,84 +1,11 @@
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.PythonCodeGenerator;
-import org.zwobble.couscous.backends.python.PythonCompiler;
-import org.zwobble.couscous.backends.python.PythonSerializer;
import org.zwobble.couscous.tests.BackendMethodTests;
import org.zwobble.couscous.tests.MethodRunner;
-import org.zwobble.couscous.values.PrimitiveValue;
-import org.zwobble.couscous.values.PrimitiveValues;
-
-import com.google.common.base.Charsets;
-import com.google.common.base.Joiner;
-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.PrimitiveValues.value;
-
-import lombok.SneakyThrows;
-import lombok.val;
public class PythonCompilerMethodTests extends BackendMethodTests {
@Override
protected MethodRunner buildMethodRunner() {
- return new MethodRunner() {
- @Override
- @SneakyThrows
- public PrimitiveValue runMethod(
- ClassNode classNode,
- String methodName,
- List<PrimitiveValue> arguments) {
- val compiler = new PythonCompiler();
- val directoryPath = Files.createTempDirectory(null);
- try {
- compiler.compile(asList(classNode), directoryPath);
-
- val argumentsString = Joiner.on(", ").join(arguments.stream()
- .map(PythonCodeGenerator::generateCode)
- .map(PythonSerializer::serialize)
- .iterator());
-
- val program = "from " + classNode.getName() + " import " + classNode.getLocalName() +
- ";print(repr(" + classNode.getLocalName() + "." + methodName + "(" + argumentsString + ")))";
-
- 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 {
- return readPrimitive(output);
- }
- } finally {
- deleteRecursively(directoryPath.toFile());
- }
- }
-
- private PrimitiveValue readPrimitive(String output) {
- if (output.equals("None")) {
- return PrimitiveValues.UNIT;
- } else if (output.startsWith("'")) {
- return value(output.substring(1, output.length() - 1));
- } else {
- return value(parseInt(output));
- }
- }
-
- private String readString(final InputStream stream) throws IOException {
- return CharStreams.toString(new InputStreamReader(stream, Charsets.UTF_8));
- }
- };
+ return new PythonMethodRunner();
}
}
diff --git a/src/test/java/org/zwobble/couscous/tests/backends/python/PythonMethodRunner.java b/src/test/java/org/zwobble/couscous/tests/backends/python/PythonMethodRunner.java
new file mode 100644
index 0000000..fb0e880
--- /dev/null
+++ b/src/test/java/org/zwobble/couscous/tests/backends/python/PythonMethodRunner.java
@@ -0,0 +1,78 @@
+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.PythonCodeGenerator;
+import org.zwobble.couscous.backends.python.PythonCompiler;
+import org.zwobble.couscous.backends.python.PythonSerializer;
+import org.zwobble.couscous.tests.MethodRunner;
+import org.zwobble.couscous.values.PrimitiveValue;
+import org.zwobble.couscous.values.PrimitiveValues;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
+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.PrimitiveValues.value;
+
+import lombok.SneakyThrows;
+import lombok.val;
+
+public class PythonMethodRunner implements MethodRunner {
+ @Override
+ @SneakyThrows
+ public PrimitiveValue runMethod(
+ ClassNode classNode,
+ String methodName,
+ List<PrimitiveValue> arguments) {
+ val compiler = new PythonCompiler();
+ val directoryPath = Files.createTempDirectory(null);
+ try {
+ compiler.compile(asList(classNode), directoryPath);
+
+ val argumentsString = Joiner.on(", ").join(arguments.stream()
+ .map(PythonCodeGenerator::generateCode)
+ .map(PythonSerializer::serialize)
+ .iterator());
+
+ val program = "from " + classNode.getName() + " import " + classNode.getLocalName() +
+ ";print(repr(" + classNode.getLocalName() + "." + methodName + "(" + argumentsString + ")))";
+
+ 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 {
+ return readPrimitive(output);
+ }
+ } finally {
+ deleteRecursively(directoryPath.toFile());
+ }
+ }
+
+ private PrimitiveValue readPrimitive(String output) {
+ if (output.equals("None")) {
+ return PrimitiveValues.UNIT;
+ } else if (output.startsWith("'")) {
+ return value(output.substring(1, output.length() - 1));
+ } else {
+ return value(parseInt(output));
+ }
+ }
+
+ 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
index 2835034..60aed36 100644
--- a/src/test/java/org/zwobble/couscous/tests/backends/python/PythonSerializerTests.java
+++ b/src/test/java/org/zwobble/couscous/tests/backends/python/PythonSerializerTests.java
@@ -9,6 +9,7 @@ import static org.junit.Assert.assertEquals;
import static org.zwobble.couscous.backends.python.PythonSerializer.serialize;
import static org.zwobble.couscous.backends.python.ast.PythonAssignmentNode.pythonAssignment;
import static org.zwobble.couscous.backends.python.ast.PythonBooleanLiteralNode.pythonBooleanLiteral;
+import static org.zwobble.couscous.backends.python.ast.PythonConditionalExpressionNode.pythonConditionalExpression;
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;
@@ -44,6 +45,15 @@ public class PythonSerializerTests {
}
@Test
+ public void conditionalExpressionIsSerializedUsingParens() {
+ assertEquals("(1) if (True) else (2)", serialize(
+ pythonConditionalExpression(
+ pythonBooleanLiteral(true),
+ pythonIntegerLiteral(1),
+ pythonIntegerLiteral(2))));
+ }
+
+ @Test
public void returnKeywordIsUsedForReturns() {
val output = serialize(pythonReturn(pythonIntegerLiteral(42)));
assertEquals("return 42\n", output);
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 40cdf8f..0a2a947 100644
--- a/src/test/java/org/zwobble/couscous/tests/interpreter/EvaluatorTests.java
+++ b/src/test/java/org/zwobble/couscous/tests/interpreter/EvaluatorTests.java
@@ -4,6 +4,7 @@ import org.junit.Test;
import org.zwobble.couscous.JavaProject;
import org.zwobble.couscous.MapBackedProject;
import org.zwobble.couscous.ast.ClassNode;
+import org.zwobble.couscous.ast.ExpressionNode;
import org.zwobble.couscous.ast.MethodNode;
import org.zwobble.couscous.ast.ReturnNode;
import org.zwobble.couscous.ast.TernaryConditionalNode;
@@ -16,7 +17,9 @@ import org.zwobble.couscous.interpreter.WrongNumberOfArguments;
import org.zwobble.couscous.interpreter.values.ConcreteType;
import org.zwobble.couscous.interpreter.values.IntegerInterpreterValue;
import org.zwobble.couscous.interpreter.values.StringInterpreterValue;
+import org.zwobble.couscous.tests.BackendEvalTests;
import org.zwobble.couscous.values.IntegerValue;
+import org.zwobble.couscous.values.PrimitiveValue;
import org.zwobble.couscous.values.StringValue;
import com.google.common.collect.ImmutableMap;
@@ -34,7 +37,7 @@ import static org.zwobble.couscous.tests.util.ExtraAsserts.assertThrows;
import lombok.val;
-public class EvaluatorTests {
+public class EvaluatorTests extends BackendEvalTests {
@Test
public void valueOfAssignmentExpressionIsNewValue() {
val arg = formalArg(var(42, "x", StringValue.REF));
@@ -46,20 +49,6 @@ public class EvaluatorTests {
}
@Test
- public void whenConditionIsTrueThenValueOfConditionalTernaryIsTrueBranch() {
- val result = eval(emptyEnvironment(),
- new TernaryConditionalNode(literal(true), literal("T"), literal("F")));
- assertEquals(new StringInterpreterValue("T"), result);
- }
-
- @Test
- public void whenConditionIsFalseThenValueOfConditionalTernaryIsFalseBranch() {
- val result = eval(emptyEnvironment(),
- new TernaryConditionalNode(literal(false), literal("T"), literal("F")));
- assertEquals(new StringInterpreterValue("F"), result);
- }
-
- @Test
public void errorIfConditionIsNotBoolean() {
val exception = assertThrows(ConditionMustBeBoolean.class,
() -> eval(emptyEnvironment(),
@@ -134,4 +123,9 @@ public class EvaluatorTests {
JavaProject.builder().build(),
ImmutableMap.of());
}
+
+ @Override
+ protected PrimitiveValue evalExpression(ExpressionNode expression) {
+ return eval(emptyEnvironment(), expression).toPrimitiveValue().get();
+ }
}