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);
}
}
}
|