aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/java/org/zwobble/couscous/frontends/java/JavaFrontend.java112
-rw-r--r--src/main/java/org/zwobble/couscous/frontends/java/JavaParser.java31
-rw-r--r--src/test/java/org/zwobble/couscous/tests/JavaToPythonTests.java56
3 files changed, 199 insertions, 0 deletions
diff --git a/src/main/java/org/zwobble/couscous/frontends/java/JavaFrontend.java b/src/main/java/org/zwobble/couscous/frontends/java/JavaFrontend.java
new file mode 100644
index 0000000..53525ce
--- /dev/null
+++ b/src/main/java/org/zwobble/couscous/frontends/java/JavaFrontend.java
@@ -0,0 +1,112 @@
+package org.zwobble.couscous.frontends.java;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.core.dom.ASTNode;
+import org.eclipse.jdt.core.dom.BooleanLiteral;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.dom.Expression;
+import org.eclipse.jdt.core.dom.NumberLiteral;
+import org.eclipse.jdt.core.dom.ReturnStatement;
+import org.eclipse.jdt.core.dom.Statement;
+import org.eclipse.jdt.core.dom.StringLiteral;
+import org.eclipse.jdt.core.dom.TypeDeclaration;
+import org.zwobble.couscous.ast.ClassNode;
+import org.zwobble.couscous.ast.ClassNodeBuilder;
+import org.zwobble.couscous.ast.ExpressionNode;
+import org.zwobble.couscous.ast.LiteralNode;
+import org.zwobble.couscous.ast.ReturnNode;
+import org.zwobble.couscous.ast.StatementNode;
+
+import lombok.SneakyThrows;
+import lombok.val;
+
+public class JavaFrontend {
+
+ public static List<ClassNode> readSourceDirectory(Path directoryPath) throws IOException {
+ val parser = new JavaParser();
+
+ return findJavaFiles(directoryPath)
+ .map(javaFile -> readClassFromFile(parser, javaFile))
+ .collect(Collectors.toList());
+ }
+
+ @SneakyThrows
+ private static ClassNode readClassFromFile(JavaParser parser, Path path) {
+ val ast = parser.parseCompilationUnit(path);
+ val name = generateClassName(ast);
+
+ val classBuilder = new ClassNodeBuilder(name);
+
+ val type = (TypeDeclaration)ast.types().get(0);
+
+ for (val method : type.getMethods()) {
+ classBuilder.method(
+ method.getName().getIdentifier(),
+ true,
+ builder -> {
+ for (Object statement : method.getBody().statements()) {
+ builder.statement(readStatement((Statement)statement));
+ }
+ return builder;
+ });
+ }
+
+ return classBuilder
+ .build();
+ }
+
+ private static StatementNode readStatement(Statement statement) {
+ switch (statement.getNodeType()) {
+ case ASTNode.RETURN_STATEMENT:
+ return readReturnStatement((ReturnStatement)statement);
+ default:
+ throw new RuntimeException("Unsupported statement: " + statement.getClass());
+ }
+ }
+
+ private static StatementNode readReturnStatement(ReturnStatement statement) {
+ return new ReturnNode(readExpression(statement.getExpression()));
+ }
+
+ private static ExpressionNode readExpression(Expression expression) {
+ switch (expression.getNodeType()) {
+ case ASTNode.BOOLEAN_LITERAL:
+ return readBooleanLiteral((BooleanLiteral)expression);
+ case ASTNode.NUMBER_LITERAL:
+ return readNumberLiteral((NumberLiteral)expression);
+ case ASTNode.STRING_LITERAL:
+ return readStringLiteral((StringLiteral)expression);
+ default:
+ throw new RuntimeException("Unsupported expression: " + expression.getClass());
+ }
+ }
+
+ private static ExpressionNode readBooleanLiteral(BooleanLiteral expression) {
+ return LiteralNode.literal(expression.booleanValue());
+ }
+
+ private static LiteralNode readNumberLiteral(NumberLiteral expression) {
+ return LiteralNode.literal(Integer.parseInt(expression.getToken()));
+ }
+
+ private static ExpressionNode readStringLiteral(StringLiteral expression) {
+ return LiteralNode.literal(expression.getLiteralValue());
+ }
+
+ private static String generateClassName(CompilationUnit ast) {
+ val type = (TypeDeclaration)ast.types().get(0);
+ return ast.getPackage().getName().getFullyQualifiedName() + "." + type.getName().getFullyQualifiedName();
+ }
+
+ private static Stream<Path> findJavaFiles(Path directoryPath) throws IOException {
+ return Files.walk(directoryPath)
+ .filter(path -> path.toFile().isFile() && path.toString().endsWith(".java"));
+ }
+
+}
diff --git a/src/main/java/org/zwobble/couscous/frontends/java/JavaParser.java b/src/main/java/org/zwobble/couscous/frontends/java/JavaParser.java
new file mode 100644
index 0000000..5be2487
--- /dev/null
+++ b/src/main/java/org/zwobble/couscous/frontends/java/JavaParser.java
@@ -0,0 +1,31 @@
+package org.zwobble.couscous.frontends.java;
+
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.eclipse.jdt.core.dom.AST;
+import org.eclipse.jdt.core.dom.ASTParser;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+
+import com.google.common.base.Charsets;
+
+import lombok.SneakyThrows;
+import lombok.val;
+
+public class JavaParser {
+ private final ASTParser parser;
+
+ public JavaParser() {
+ parser = ASTParser.newParser(AST.JLS8);
+ parser.setResolveBindings(true);
+ }
+
+ @SneakyThrows
+ public CompilationUnit parseCompilationUnit(Path path) {
+ val javaFileBytes = Files.readAllBytes(path);
+ val javaFileChars = Charsets.UTF_8.decode(ByteBuffer.wrap(javaFileBytes));
+ parser.setSource(javaFileChars.array());
+ return (CompilationUnit)parser.createAST(null);
+ }
+}
diff --git a/src/test/java/org/zwobble/couscous/tests/JavaToPythonTests.java b/src/test/java/org/zwobble/couscous/tests/JavaToPythonTests.java
new file mode 100644
index 0000000..cbd8ca5
--- /dev/null
+++ b/src/test/java/org/zwobble/couscous/tests/JavaToPythonTests.java
@@ -0,0 +1,56 @@
+package org.zwobble.couscous.tests;
+
+import java.nio.file.Files;
+
+import org.junit.Test;
+import org.zwobble.couscous.ast.TypeName;
+import org.zwobble.couscous.frontends.java.JavaFrontend;
+import org.zwobble.couscous.tests.backends.python.PythonMethodRunner;
+import org.zwobble.couscous.values.PrimitiveValue;
+
+import static java.util.Arrays.asList;
+import static org.junit.Assert.assertEquals;
+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 JavaToPythonTests {
+ @Test
+ public void canEvaluateLiterals() {
+ assertEquals(value("hello"), evalExpression("\"hello\""));
+ assertEquals(value(true), evalExpression("true"));
+ assertEquals(value(false), evalExpression("false"));
+ assertEquals(value(42), evalExpression("42"));
+ }
+
+ @SneakyThrows
+ private PrimitiveValue evalExpression(String expressionSource) {
+ val javaClass =
+ "package com.example;" +
+ "public class Example {" +
+ " public static Object main() {" +
+ " return " + expressionSource + ";" +
+ " }" +
+ "}";
+
+ val directoryPath = Files.createTempDirectory(null);
+ try {
+ Files.createDirectories(directoryPath.resolve("com/example"));
+ Files.write(
+ directoryPath.resolve("com/example/Example.java"),
+ asList(javaClass));
+
+ val classNodes = JavaFrontend.readSourceDirectory(directoryPath);
+
+ return new PythonMethodRunner().runMethod(
+ classNodes,
+ TypeName.of("com.example.Example"),
+ "main",
+ asList());
+ } finally {
+ deleteRecursively(directoryPath.toFile());
+ }
+ }
+}