aboutsummaryrefslogtreecommitdiff
path: root/src/test/java/org/zwobble/couscous/tests/backends/python/PythonMethodRunner.java
blob: a1a58a38af16ffd5f039e72198342f7c893e3287 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package org.zwobble.couscous.tests.backends.python;

import com.google.common.base.Joiner;
import org.hamcrest.Matchers;
import org.zwobble.couscous.ast.MethodSignature;
import org.zwobble.couscous.tests.util.processes.ExecutionResult;
import org.zwobble.couscous.types.ScalarType;
import org.zwobble.couscous.ast.TypeNode;
import org.zwobble.couscous.types.Type;
import org.zwobble.couscous.backends.naming.Names;
import org.zwobble.couscous.backends.python.PythonBackend;
import org.zwobble.couscous.backends.python.PythonCodeGenerator;
import org.zwobble.couscous.backends.python.PythonSerializer;
import org.zwobble.couscous.tests.MethodRunner;
import org.zwobble.couscous.tests.util.processes.Processes;
import org.zwobble.couscous.values.PrimitiveValue;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

import static java.lang.Integer.parseInt;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.zwobble.couscous.ast.MethodSignature.signature;
import static org.zwobble.couscous.tests.util.ExtraFiles.deleteRecursively;
import static org.zwobble.couscous.util.ExtraLists.eagerMap;
import static org.zwobble.couscous.util.ExtraLists.list;
import static org.zwobble.couscous.values.PrimitiveValues.UNIT;
import static org.zwobble.couscous.values.PrimitiveValues.value;

public class PythonMethodRunner implements MethodRunner {
    @Override
    public PrimitiveValue runMethod(List<TypeNode> classNodes, ScalarType className, String methodName, List<PrimitiveValue> arguments, Type returnType) {
        try {
            Path directoryPath = Files.createTempDirectory(null);
            PythonBackend compiler = new PythonBackend(directoryPath, "couscous");
            try {
                compiler.compile(classNodes);
                return runFunction(directoryPath, className, methodName, arguments, returnType);
            } finally {
                deleteRecursively(directoryPath.toFile());
            }
        } catch (final java.lang.Throwable $ex) {
            throw new RuntimeException($ex);
        }
    }

    public static PrimitiveValue runFunction(
            Path directoryPath,
            ScalarType className,
            String methodName,
            List<PrimitiveValue> arguments,
            Type returnType)
            throws IOException, InterruptedException {

        String argumentsString = Joiner.on(", ").join(arguments.stream().map(PythonCodeGenerator::generateCode).map(PythonSerializer::serialize).iterator());
        MethodSignature signature = signature(
            methodName,
            eagerMap(arguments, argument -> argument.getType()),
            returnType);
        String pythonMethodName = Names.toUniqueName(signature);
        String program = "from couscous." + className.getQualifiedName() + " import " + className.getSimpleName() + ";" +
            "value = " + className.getSimpleName() + "." + pythonMethodName + "(" + argumentsString + ");" +
            "print(type(value)); print(value)";
        ExecutionResult result = Processes.run(list("python3.11", "-c", program), directoryPath);
        result.assertSuccess();
        return readPrimitive(result.getStdout());
    }

    private static final String PACKAGE_PREFIX = "couscous.";

    private static PrimitiveValue readPrimitive(String output) {
        String[] lines = output.split("[\\r\\n]+");
        String type = lines[0];
        String value = lines[1];
        switch (type) {
            case "<class 'int'>":
                return value(parseInt(value));
            case "<class 'str'>":
                return value(value);
            case "<class 'bool'>":
                return value(value.equals("True"));
            case "<class 'NoneType'>":
                return UNIT;
            case "<class 'type'>":
                String typeName = value.substring(value.indexOf("'") + 1, value.lastIndexOf("'"));
                assertThat(typeName, Matchers.startsWith(PACKAGE_PREFIX));
                return value(ScalarType.topLevel(typeName.substring(PACKAGE_PREFIX.length(), typeName.lastIndexOf("."))));
            default:
                throw new RuntimeException("Unhandled type: " + type);
        }
    }
}