From a2f225296516f5e4dd09cd406ca2f2a5b568d39d Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Thu, 7 Sep 2023 01:51:50 -0700 Subject: [PATCH] ffi: autogen java bindings (very wip) --- examples/java.pork | 10 +- ffi/build.gradle.kts | 1 + .../kotlin/gay/pizza/pork/ffi/JavaAutogen.kt | 94 +++++++++++++++++++ .../pork/ffi/JavaAutogenContentSource.kt | 19 ++++ .../pizza/pork/ffi/JavaFunctionDefinition.kt | 8 ++ .../kotlin/gay/pizza/pork/parser/Printer.kt | 3 + .../main/kotlin/gay/pizza/pork/tool/Tool.kt | 2 + 7 files changed, 131 insertions(+), 6 deletions(-) create mode 100644 ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaAutogen.kt create mode 100644 ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaAutogenContentSource.kt diff --git a/examples/java.pork b/examples/java.pork index 1acd09b..4c1ab86 100644 --- a/examples/java.pork +++ b/examples/java.pork @@ -1,10 +1,8 @@ -func java_system_err() - native java "java.lang.System:static-getter:err:java.io.PrintStream" +import java java.lang.System -func print_stream_println(stream, line) - native java "java.io.PrintStream:virtual:println:void:String" +func java_io_PrintStream_println(a) native java "java.io.PrintStream:virtual:println:void:String" export func main() { - let error = java_system_err() - print_stream_println(error, "Hello World") + let stream = java_lang_System_err_get() + java_io_PrintStream_println(stream, "Hello World") } diff --git a/ffi/build.gradle.kts b/ffi/build.gradle.kts index 3e5b11b..a33eb75 100644 --- a/ffi/build.gradle.kts +++ b/ffi/build.gradle.kts @@ -3,6 +3,7 @@ plugins { } dependencies { + api(project(":frontend")) api(project(":evaluator")) implementation(project(":common")) diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaAutogen.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaAutogen.kt new file mode 100644 index 0000000..3bddef9 --- /dev/null +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaAutogen.kt @@ -0,0 +1,94 @@ +package gay.pizza.pork.ffi + +import gay.pizza.pork.ast.* +import java.io.PrintStream +import java.lang.reflect.Modifier + +class JavaAutogen(val javaClass: Class<*>) { + private val prefix = javaClass.name.replace(".", "_") + + fun generateCompilationUnit(): CompilationUnit { + return CompilationUnit( + declarations = listOf(), + definitions = generateFunctionDefinitions() + ) + } + + fun generateFunctionDefinitions(): List { + val definitions = mutableMapOf() + for (method in javaClass.methods) { + if (!Modifier.isPublic(method.modifiers)) { + continue + } + + val name = method.name + val returnTypeName = method.returnType.name + val parameterNames = method.parameters.indices.map { ('a' + it).toString() } + val parameterTypeNames = method.parameters.map { it.type.name } + + fun form(kind: String): JavaFunctionDefinition = + JavaFunctionDefinition(javaClass.name, kind, name, returnTypeName, parameterTypeNames) + + if (Modifier.isStatic(method.modifiers)) { + definitions[name] = function(name, parameterNames, form("static")) + } else { + definitions[name] = function(name, parameterNames, form("virtual")) + } + } + + for (field in javaClass.fields) { + if (!Modifier.isPublic(field.modifiers)) { + continue + } + + val name = field.name + val valueTypeName = field.type.name + val isStatic = Modifier.isStatic(field.modifiers) + fun form(kind: String, getOrSet: Boolean): JavaFunctionDefinition = + JavaFunctionDefinition(javaClass.name, kind, name, valueTypeName, if (getOrSet) { + if (isStatic) { + emptyList() + } else { + listOf(javaClass.name) + } + } else { + if (isStatic) { + listOf(valueTypeName) + } else { + listOf(javaClass.name, valueTypeName) + } + }) + + val parametersForGetter = if (isStatic) { + emptyList() + } else { + listOf("object") + } + + val parametersForSetter = if (isStatic) { + listOf("value") + } else { + listOf("object", "value") + } + + val getterKind = if (isStatic) "static-getter" else "getter" + val setterKind = if (isStatic) "static-setter" else "setter" + definitions[name + "_get"] = function(name + "_get", parametersForGetter, form(getterKind, true)) + definitions[name + "_set"] = function(name + "_set", parametersForSetter, form(setterKind, false)) + } + + return definitions.values.toList() + } + + private fun function(name: String, parameterNames: List, functionDefinition: JavaFunctionDefinition): FunctionDefinition = + FunctionDefinition( + modifiers = DefinitionModifiers(true), + symbol = Symbol("${prefix}_${name}"), + arguments = parameterNames.map { Symbol(it) }, + native = asNative(functionDefinition), + block = null + ) + + private fun asNative(functionDefinition: JavaFunctionDefinition): Native = + Native(Symbol("java"), StringLiteral(functionDefinition.encode())) +} diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaAutogenContentSource.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaAutogenContentSource.kt new file mode 100644 index 0000000..2a9d2fc --- /dev/null +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaAutogenContentSource.kt @@ -0,0 +1,19 @@ +package gay.pizza.pork.ffi + +import gay.pizza.pork.ast.visit +import gay.pizza.pork.frontend.ContentSource +import gay.pizza.pork.parser.CharSource +import gay.pizza.pork.parser.Printer +import gay.pizza.pork.parser.StringCharSource + +object JavaAutogenContentSource : ContentSource { + override fun loadAsCharSource(path: String): CharSource { + val javaClassName = path.replace("/", ".").substring(0, path.length - 5) + val autogen = JavaAutogen(Class.forName(javaClassName)) + val compilationUnit = autogen.generateCompilationUnit() + val content = buildString { Printer(this).visit(compilationUnit) } + return StringCharSource(content) + } + + override fun stableContentIdentity(path: String): String = path +} diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaFunctionDefinition.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaFunctionDefinition.kt index 26404b2..010449e 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaFunctionDefinition.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaFunctionDefinition.kt @@ -20,4 +20,12 @@ class JavaFunctionDefinition( return JavaFunctionDefinition(type, kind, symbol, returnType, parameters) } } + + fun encode(): String = buildString { + append("${type}:${kind}:${symbol}:${returnType}") + if (parameters.isNotEmpty()) { + append(":") + append(parameters.joinToString(",")) + } + } } diff --git a/parser/src/main/kotlin/gay/pizza/pork/parser/Printer.kt b/parser/src/main/kotlin/gay/pizza/pork/parser/Printer.kt index caa1fe4..d7d3d56 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/Printer.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/Printer.kt @@ -131,6 +131,9 @@ class Printer(buffer: StringBuilder) : NodeVisitor { } override fun visitFunctionDefinition(node: FunctionDefinition) { + if (node.modifiers.export) { + append("export ") + } append("func ") visit(node.symbol) append("(") diff --git a/tool/src/main/kotlin/gay/pizza/pork/tool/Tool.kt b/tool/src/main/kotlin/gay/pizza/pork/tool/Tool.kt index 7ad1db5..b19a887 100644 --- a/tool/src/main/kotlin/gay/pizza/pork/tool/Tool.kt +++ b/tool/src/main/kotlin/gay/pizza/pork/tool/Tool.kt @@ -6,6 +6,7 @@ import gay.pizza.pork.ast.visit import gay.pizza.pork.evaluator.CallableFunction import gay.pizza.pork.evaluator.Evaluator import gay.pizza.pork.evaluator.Scope +import gay.pizza.pork.ffi.JavaAutogenContentSource import gay.pizza.pork.frontend.ContentSource import gay.pizza.pork.frontend.ImportLocator import gay.pizza.pork.frontend.DynamicImportSource @@ -36,6 +37,7 @@ abstract class Tool { val dynamicImportSource = DynamicImportSource() dynamicImportSource.addContentSource("std", PorkStdlib) dynamicImportSource.addContentSource("local", fileContentSource) + dynamicImportSource.addContentSource("java", JavaAutogenContentSource) val world = World(dynamicImportSource) val evaluator = Evaluator(world, scope) setupEvaluator(evaluator)