diff options
| author | Michael Williamson <mike@zwobble.org> | 2015-10-05 11:04:17 +0100 |
|---|---|---|
| committer | Michael Williamson <mike@zwobble.org> | 2015-10-05 11:04:17 +0100 |
| commit | 96b1e6f776ae8ce8910c44a3bbec0b489fd10c88 (patch) | |
| tree | 86658222ef741b1620c1635d3fd98d99452a387b /src | |
| parent | cdf51cdb9b3f8cef5896315496f348d9f7220903 (diff) | |
Add the beginnings of a Python backend
Diffstat (limited to 'src')
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(); + } +} |
