summaryrefslogtreecommitdiff
path: root/transpile-tests/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'transpile-tests/src/main/java')
-rw-r--r--transpile-tests/src/main/java/com/seaofnodes/simple/transpile/Arg.java23
-rw-r--r--transpile-tests/src/main/java/com/seaofnodes/simple/transpile/AstVisitor.java103
-rw-r--r--transpile-tests/src/main/java/com/seaofnodes/simple/transpile/JUnitParser.java71
-rw-r--r--transpile-tests/src/main/java/com/seaofnodes/simple/transpile/TestClass.java6
-rw-r--r--transpile-tests/src/main/java/com/seaofnodes/simple/transpile/TestMethod.java10
-rw-r--r--transpile-tests/src/main/java/com/seaofnodes/simple/transpile/printers/RustPrinter.java108
6 files changed, 321 insertions, 0 deletions
diff --git a/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/Arg.java b/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/Arg.java
new file mode 100644
index 00000000..4334641f
--- /dev/null
+++ b/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/Arg.java
@@ -0,0 +1,23 @@
+package com.seaofnodes.simple.transpile;
+
+public sealed interface Arg {
+ record IntBot() implements Arg {
+ }
+
+ record IntTop() implements Arg {
+ }
+
+ record IntConstant(long value) implements Arg {
+ }
+
+ static Arg parse(String s) {
+ if (s.equals("TypeInteger.BOT"))
+ return new IntBot();
+ if (s.equals("TypeInteger.TOP"))
+ return new IntTop();
+ var parts = s.split("\\(|\\)");
+ if (parts.length != 2 | !parts[0].equals("TypeInteger.constant"))
+ throw new RuntimeException("Unexpected Argument " + s);
+ return new IntConstant(Long.parseLong(parts[1]));
+ }
+}
diff --git a/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/AstVisitor.java b/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/AstVisitor.java
new file mode 100644
index 00000000..581969c0
--- /dev/null
+++ b/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/AstVisitor.java
@@ -0,0 +1,103 @@
+package com.seaofnodes.simple.transpile;
+
+import com.sun.source.tree.*;
+import com.sun.source.util.TreeScanner;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+class AstVisitor extends TreeScanner<Void, Void> {
+ final HashMap<String, TestClass> results = new HashMap<>();
+
+ private String className;
+ private TestMethod current;
+ private boolean inCatch;
+
+ @Override
+ public Void visitClass(ClassTree node, Void unused) {
+ var name = node.getSimpleName().toString();
+ if (name.matches("Chapter\\d+Test")) {
+ var backup = className;
+ className = name;
+ super.visitClass(node, unused);
+ className = backup;
+ }
+ return null;
+ }
+
+ @Override
+ public Void visitMethod(MethodTree node, Void unused) {
+ if (className != null) {
+ current = new TestMethod();
+ current.name = node.getName().toString();
+ super.visitMethod(node, unused);
+ results.compute(className, (k, v) -> {
+ if (v == null)
+ v = new TestClass(className, new ArrayList<>());
+ v.methods().add(current);
+ return v;
+ });
+ current = null;
+ }
+ return null;
+ }
+
+ @Override
+ public Void visitMethodInvocation(MethodInvocationTree node, Void unused) {
+ if (current != null) {
+ if (node.getMethodSelect().toString().endsWith(".parse")) {
+ current.showAfterParse = switch (node.getArguments().size()) {
+ case 0 -> false;
+ case 1 -> (Boolean) ((LiteralTree) node.getArguments().getFirst()).getValue();
+ default -> throw new RuntimeException("unexpected parse arguments" + node);
+ };
+ }
+ if (node.getMethodSelect().toString().endsWith("assertEquals")) {
+ var args = node.getArguments();
+ if (inCatch) {
+ if (args.size() != 2)
+ throw new RuntimeException("Unexpected number of arguments " + node);
+ if (args.get(0).toString().contains("e.getMessage()")) {
+ current.parseErrorMessage = args.get(1).toString();
+ } else if (args.get(1).toString().contains("e.getMessage()")) {
+ current.parseErrorMessage = args.get(0).toString();
+ }
+ } else {
+ if (args.size() != 2)
+ throw new RuntimeException("Unexpected number of arguments " + node);
+ if (args.get(0).toString().contains("stop.toString()")) {
+ current.assertStopEquals = args.get(1).toString();
+ } else if (args.get(1).toString().contains("stop.toString()")) {
+ current.assertStopEquals = args.get(0).toString();
+ }
+ }
+ }
+ }
+ return super.visitMethodInvocation(node, unused); // otherwise we would miss the first part of new Parser("").parse()
+ }
+
+ @Override
+ public Void visitNewClass(NewClassTree node, Void unused) {
+ if (current != null) {
+ if (node.getIdentifier().toString().equals("Parser")) {
+ var args = node.getArguments();
+ switch (args.size()) {
+ case 1 -> {
+ }
+ case 2 -> current.parserArg = Arg.parse(args.get(1).toString());
+ default -> throw new RuntimeException("Parser constructor with unexpected arguments: " + node);
+ }
+ current.parserInput = args.get(0).toString();
+ }
+ }
+ return super.visitNewClass(node, unused);
+ }
+
+ @Override
+ public Void visitCatch(CatchTree node, Void unused) {
+ inCatch = true;
+ super.visitCatch(node, unused);
+ inCatch = false;
+ return null;
+ }
+}
diff --git a/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/JUnitParser.java b/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/JUnitParser.java
new file mode 100644
index 00000000..e396d56f
--- /dev/null
+++ b/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/JUnitParser.java
@@ -0,0 +1,71 @@
+package com.seaofnodes.simple.transpile;
+
+import com.sun.source.util.JavacTask;
+
+import javax.tools.ToolProvider;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.*;
+
+import static java.util.Comparator.comparing;
+
+public class JUnitParser {
+
+ /**
+ * @param simpleRepo The path to the project root
+ * @return A sorted map from chapter name to a sorted list of parsed test classes.
+ */
+ public static TreeMap<String, List<TestClass>> parseRepository(Path simpleRepo) throws IOException {
+ var chapters = new TreeMap<String, List<TestClass>>();
+ var inputs = simpleRepo.toFile().listFiles(File::isDirectory);
+ if (inputs == null)
+ throw new IOException("failed to read Simple project root");
+ Arrays.sort(inputs);
+ for (File file : inputs) {
+ var chapter = file.getName();
+ if (chapter.matches("chapter\\d+")) {
+ chapters.put(chapter, parseChapter(file.toPath()));
+ }
+ }
+ return chapters;
+ }
+
+ /**
+ * @param input The path to a chapter in the repository.
+ * @return a sorted list of parsed test classes
+ */
+ public static List<TestClass> parseChapter(Path input) throws IOException {
+ var tests = new ArrayList<Path>();
+ Files.walkFileTree(input, Set.of(), 20, new SimpleFileVisitor<>() {
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
+ if (file.getFileName().toString().matches("Chapter\\d+Test\\.java")) {
+ tests.add(file);
+ }
+ return FileVisitResult.CONTINUE;
+ }
+ });
+
+ var compiler = ToolProvider.getSystemJavaCompiler();
+ var compilationUnits = compiler
+ .getStandardFileManager(null, null, null)
+ .getJavaFileObjectsFromPaths(tests);
+
+ var task = (JavacTask) compiler.getTask(null, null, null, null, null, compilationUnits);
+
+ var visitor = new AstVisitor();
+ for (var tree : task.parse()) {
+ tree.accept(visitor, null);
+ }
+
+ var testClasses = new ArrayList<>(visitor.results.values());
+ testClasses.sort(comparing(TestClass::name));
+ return testClasses;
+ }
+}
+
diff --git a/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/TestClass.java b/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/TestClass.java
new file mode 100644
index 00000000..5bb5609c
--- /dev/null
+++ b/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/TestClass.java
@@ -0,0 +1,6 @@
+package com.seaofnodes.simple.transpile;
+
+import java.util.ArrayList;
+
+public record TestClass(String name, ArrayList<TestMethod> methods) {
+}
diff --git a/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/TestMethod.java b/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/TestMethod.java
new file mode 100644
index 00000000..56357c4a
--- /dev/null
+++ b/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/TestMethod.java
@@ -0,0 +1,10 @@
+package com.seaofnodes.simple.transpile;
+
+public class TestMethod {
+ public String name;
+ public String parserInput;
+ public Arg parserArg;
+ public String parseErrorMessage;
+ public boolean showAfterParse;
+ public String assertStopEquals;
+}
diff --git a/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/printers/RustPrinter.java b/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/printers/RustPrinter.java
new file mode 100644
index 00000000..7362ef60
--- /dev/null
+++ b/transpile-tests/src/main/java/com/seaofnodes/simple/transpile/printers/RustPrinter.java
@@ -0,0 +1,108 @@
+package com.seaofnodes.simple.transpile.printers;
+
+import com.seaofnodes.simple.transpile.Arg;
+import com.seaofnodes.simple.transpile.JUnitParser;
+import com.seaofnodes.simple.transpile.TestClass;
+import com.seaofnodes.simple.transpile.TestMethod;
+
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+public class RustPrinter {
+
+ public static void main(String[] args) throws Exception {
+ if (args.length != 2) {
+ System.out.println("Usage: <path-to-simple-repo> <output-dir>");
+ System.exit(1);
+ }
+ var printer = new RustPrinter();
+ var output = Path.of(args[1]);
+
+ for (var e : JUnitParser.parseRepository(Path.of(args[0])).entrySet()) {
+ printer.printToFile(e.getKey(), e.getValue(), output);
+ }
+ }
+
+ private void printToFile(String chapter, List<TestClass> testClasses, Path outputDirectory) throws IOException, InterruptedException {
+ var output = outputDirectory.resolve(chapter);
+ Files.createDirectories(output);
+ for (var testClass : testClasses) {
+ var outputFile = output.resolve(testClass.name().replace("Test", "").toLowerCase() + ".rs");
+ System.out.println(outputFile);
+ Files.write(outputFile, printAndRustfmt(testClass.methods()));
+ }
+ }
+
+ private byte[] printAndRustfmt(ArrayList<TestMethod> tests) throws IOException, InterruptedException {
+ var p = Runtime.getRuntime().exec(new String[]{"rustfmt"});
+ try (var print = new PrintStream(new BufferedOutputStream(p.getOutputStream()))) {
+ printFile(tests, print);
+ }
+ p.waitFor();
+
+ var errors = new String(p.getErrorStream().readAllBytes(), StandardCharsets.UTF_8);
+ if (!errors.isEmpty() || p.exitValue() != 0) {
+ throw new RuntimeException("rustfmt exit=" + p.exitValue() + " stderr=" + errors);
+ }
+
+ return p.getInputStream().readAllBytes();
+ }
+
+ private void printFile(ArrayList<TestMethod> tests, PrintStream out) {
+ out.println("""
+ use crate::datastructures::arena::DroplessArena;
+ use crate::sea_of_nodes::parser::Parser;
+ use crate::sea_of_nodes::types::Types;
+ """);
+
+ for (TestMethod test : tests) {
+ if (test.parseErrorMessage != null) {
+ out.println("use crate::sea_of_nodes::tests::test_error;");
+ break;
+ }
+ }
+
+ for (TestMethod test : tests) {
+ printTest(test, out);
+ }
+ }
+
+ private void printTest(TestMethod test, PrintStream out) {
+ out.println();
+ out.println("#[test]");
+ out.println("fn " + test.name + "() {");
+ if (test.parseErrorMessage != null) {
+ out.println("test_error(" + test.parserInput + ", " + test.parseErrorMessage + ")");
+ } else if (test.parserInput != null) {
+ out.println("let arena = DroplessArena::new();");
+ out.println("let types = Types::new(&arena);");
+ out.printf("let mut parser = Parser::new(%s%s, &types);\n", multiline(test.parserInput), switch (test.parserArg) {
+ case null -> "";
+ case Arg.IntBot ignored -> ", types.ty_int_bot";
+ case Arg.IntTop ignored -> ", types.ty_int_top";
+ case Arg.IntConstant c -> ", types.get_int(" + c.value() + ")";
+ });
+ out.println("let stop = parser.parse().unwrap();");
+ if (test.showAfterParse) {
+ out.println("parser.show_graph();");
+ }
+ if (test.assertStopEquals != null) {
+ out.printf("assert_eq!(parser.print(stop), %s);\n", test.assertStopEquals);
+ }
+ }
+ out.println("}");
+ }
+
+ private String multiline(String string) {
+ if (!string.contains("\\n")) {
+ return string;
+ }
+ return string.replaceFirst("^\"", "\"\\\\\\\\n").replace("\\n", "\n");
+ }
+}