ffi: autogen java bindings (very wip)

This commit is contained in:
Alex Zenla 2023-09-07 01:51:50 -07:00
parent 540826fb6e
commit a2f2252965
Signed by: alex
GPG Key ID: C0780728420EBFE5
7 changed files with 131 additions and 6 deletions

View File

@ -1,10 +1,8 @@
func java_system_err() import java java.lang.System
native java "java.lang.System:static-getter:err:java.io.PrintStream"
func print_stream_println(stream, line) func java_io_PrintStream_println(a) native java "java.io.PrintStream:virtual:println:void:String"
native java "java.io.PrintStream:virtual:println:void:String"
export func main() { export func main() {
let error = java_system_err() let stream = java_lang_System_err_get()
print_stream_println(error, "Hello World") java_io_PrintStream_println(stream, "Hello World")
} }

View File

@ -3,6 +3,7 @@ plugins {
} }
dependencies { dependencies {
api(project(":frontend"))
api(project(":evaluator")) api(project(":evaluator"))
implementation(project(":common")) implementation(project(":common"))

View File

@ -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<FunctionDefinition> {
val definitions = mutableMapOf<String, FunctionDefinition>()
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<String>, 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()))
}

View File

@ -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
}

View File

@ -20,4 +20,12 @@ class JavaFunctionDefinition(
return JavaFunctionDefinition(type, kind, symbol, returnType, parameters) return JavaFunctionDefinition(type, kind, symbol, returnType, parameters)
} }
} }
fun encode(): String = buildString {
append("${type}:${kind}:${symbol}:${returnType}")
if (parameters.isNotEmpty()) {
append(":")
append(parameters.joinToString(","))
}
}
} }

View File

@ -131,6 +131,9 @@ class Printer(buffer: StringBuilder) : NodeVisitor<Unit> {
} }
override fun visitFunctionDefinition(node: FunctionDefinition) { override fun visitFunctionDefinition(node: FunctionDefinition) {
if (node.modifiers.export) {
append("export ")
}
append("func ") append("func ")
visit(node.symbol) visit(node.symbol)
append("(") append("(")

View File

@ -6,6 +6,7 @@ import gay.pizza.pork.ast.visit
import gay.pizza.pork.evaluator.CallableFunction import gay.pizza.pork.evaluator.CallableFunction
import gay.pizza.pork.evaluator.Evaluator import gay.pizza.pork.evaluator.Evaluator
import gay.pizza.pork.evaluator.Scope import gay.pizza.pork.evaluator.Scope
import gay.pizza.pork.ffi.JavaAutogenContentSource
import gay.pizza.pork.frontend.ContentSource import gay.pizza.pork.frontend.ContentSource
import gay.pizza.pork.frontend.ImportLocator import gay.pizza.pork.frontend.ImportLocator
import gay.pizza.pork.frontend.DynamicImportSource import gay.pizza.pork.frontend.DynamicImportSource
@ -36,6 +37,7 @@ abstract class Tool {
val dynamicImportSource = DynamicImportSource() val dynamicImportSource = DynamicImportSource()
dynamicImportSource.addContentSource("std", PorkStdlib) dynamicImportSource.addContentSource("std", PorkStdlib)
dynamicImportSource.addContentSource("local", fileContentSource) dynamicImportSource.addContentSource("local", fileContentSource)
dynamicImportSource.addContentSource("java", JavaAutogenContentSource)
val world = World(dynamicImportSource) val world = World(dynamicImportSource)
val evaluator = Evaluator(world, scope) val evaluator = Evaluator(world, scope)
setupEvaluator(evaluator) setupEvaluator(evaluator)