From 83c24843a32601585962e605f63878fe63fff656 Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Fri, 22 Sep 2023 00:26:24 -0700 Subject: [PATCH] ffi: new native function format --- ast/src/main/ast/pork.yml | 4 +-- .../main/kotlin/gay/pizza/pork/ast/Native.kt | 8 ++--- .../pizza/pork/evaluator/FunctionContext.kt | 2 +- .../pork/evaluator/InternalNativeProvider.kt | 3 +- .../pizza/pork/evaluator/NativeProvider.kt | 2 +- examples/gameoflife/SDL2.pork | 30 +++++++++---------- .../pizza/pork/ffi/FfiFunctionDefinition.kt | 27 +++++++++++------ .../kotlin/gay/pizza/pork/ffi/JavaAutogen.kt | 2 +- .../pizza/pork/ffi/JavaFunctionDefinition.kt | 26 ++++++++-------- .../gay/pizza/pork/ffi/JavaNativeProvider.kt | 4 +-- .../gay/pizza/pork/ffi/JnaNativeProvider.kt | 4 +-- .../kotlin/gay/pizza/pork/parser/Parser.kt | 7 +++-- .../kotlin/gay/pizza/pork/parser/Printer.kt | 7 ++++- stdlib/src/main/pork/ffi/malloc.pork | 6 ++-- 14 files changed, 75 insertions(+), 57 deletions(-) diff --git a/ast/src/main/ast/pork.yml b/ast/src/main/ast/pork.yml index 199c562..5d3c05b 100644 --- a/ast/src/main/ast/pork.yml +++ b/ast/src/main/ast/pork.yml @@ -300,8 +300,8 @@ types: values: - name: form type: Symbol - - name: definition - type: StringLiteral + - name: definitions + type: List IndexedBy: parent: Expression values: diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/Native.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/Native.kt index 3e56ed2..549402d 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/Native.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/Native.kt @@ -6,23 +6,23 @@ import kotlinx.serialization.Serializable @Serializable @SerialName("native") -class Native(val form: Symbol, val definition: StringLiteral) : Node() { +class Native(val form: Symbol, val definitions: List) : Node() { override val type: NodeType = NodeType.Native override fun visitChildren(visitor: NodeVisitor): List = - visitor.visitNodes(form, definition) + visitor.visitAll(listOf(form), definitions) override fun visit(visitor: NodeVisitor): T = visitor.visitNative(this) override fun equals(other: Any?): Boolean { if (other !is Native) return false - return other.form == form && other.definition == definition + return other.form == form && other.definitions == definitions } override fun hashCode(): Int { var result = form.hashCode() - result = 31 * result + definition.hashCode() + result = 31 * result + definitions.hashCode() result = 31 * result + type.hashCode() return result } diff --git a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/FunctionContext.kt b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/FunctionContext.kt index addaf81..46ae929 100644 --- a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/FunctionContext.kt +++ b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/FunctionContext.kt @@ -11,7 +11,7 @@ class FunctionContext(val compilationUnitContext: CompilationUnitContext, val no val native = node.native!! val nativeFunctionProvider = compilationUnitContext.evaluator.nativeFunctionProvider(native.form.id) - nativeFunctionProvider.provideNativeFunction(native.definition.text, node.arguments) + nativeFunctionProvider.provideNativeFunction(native.definitions.map { it.text }, node.arguments) } private val nativeCached by lazy { resolveMaybeNative() } diff --git a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/InternalNativeProvider.kt b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/InternalNativeProvider.kt index 309e587..dd9e62b 100644 --- a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/InternalNativeProvider.kt +++ b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/InternalNativeProvider.kt @@ -10,7 +10,8 @@ class InternalNativeProvider(val quiet: Boolean = false) : NativeProvider { "listInitWith" to CallableFunction(::listInitWith) ) - override fun provideNativeFunction(definition: String, arguments: List): CallableFunction { + override fun provideNativeFunction(definitions: List, arguments: List): CallableFunction { + val definition = definitions[0] return functions[definition] ?: throw RuntimeException("Unknown Internal Function: $definition") } diff --git a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/NativeProvider.kt b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/NativeProvider.kt index 95483f7..67000ac 100644 --- a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/NativeProvider.kt +++ b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/NativeProvider.kt @@ -3,5 +3,5 @@ package gay.pizza.pork.evaluator import gay.pizza.pork.ast.ArgumentSpec interface NativeProvider { - fun provideNativeFunction(definition: String, arguments: List): CallableFunction + fun provideNativeFunction(definitions: List, arguments: List): CallableFunction } diff --git a/examples/gameoflife/SDL2.pork b/examples/gameoflife/SDL2.pork index 42d5df0..91f8e91 100644 --- a/examples/gameoflife/SDL2.pork +++ b/examples/gameoflife/SDL2.pork @@ -5,10 +5,10 @@ export let SDL_INIT_VIDEO = 32 export func SDL_Init(flags) - native ffi "SDL2:SDL_Init:int:unsigned int" + native ffi "SDL2" "int SDL_Init(unsigned int)" export func SDL_Quit() - native ffi "SDL2:SDL_Quit:void" + native ffi "SDL2" "void SDL_Quit()" // SDL_video.h @@ -23,18 +23,18 @@ export func SDL_WINDOWPOS_CENTERED_DISPLAY(x) { SDL_WINDOWPOS_CENTERED_MASK | x export let SDL_WINDOWPOS_CENTERED = SDL_WINDOWPOS_CENTERED_DISPLAY(0) export func SDL_CreateWindow(title, x, y, w, h, flags) - native ffi "SDL2:SDL_CreateWindow:void*:char*,int,int,int,int,unsigned int" + native ffi "SDL2" "void* SDL_CreateWindow(char*, int, int, int, int, unsigned int)" export func SDL_DestroyWindow(window) - native ffi "SDL2:SDL_DestroyWindow:void:void*" + native ffi "SDL2" "void SDL_DestroyWindow(void*)" // SDL_event.h export func SDL_PumpEvents() - native ffi "SDL2:SDL_PumpEvents:void" + native ffi "SDL2" "void SDL_PumpEvents()" export func SDL_WaitEvent(event) - native ffi "SDL2:SDL_WaitEvent:int:void*" + native ffi "SDL2" "int SDL_WaitEvent(void*)" // SDL_keyboard.h @@ -54,36 +54,36 @@ export let KMOD_CAPS = 8192 export let KMOD_SCROLL = 32768 export func SDL_GetModState() - native ffi "SDL2:SDL_GetModState:int" + native ffi "SDL2" "int SDL_GetModState()" // SDL_renderer.h export let SDL_RENDERER_PRESENTVSYNC = 4 export func SDL_CreateRenderer(window, index, flags) - native ffi "SDL2:SDL_CreateRenderer:void*:void*,int,unsigned int" + native ffi "SDL2" "void* SDL_CreateRenderer(void*, int, unsigned int)" export func SDL_DestroyRenderer(renderer) - native ffi "SDL2:SDL_DestroyRenderer:void:void*" + native ffi "SDL2" "void SDL_DestroyRenderer(void*)" export func SDL_RenderSetLogicalSize(renderer, w, h) - native ffi "SDL2:SDL_RenderSetLogicalSize:int:void*,int,int" + native ffi "SDL2" "int SDL_RenderSetLogicalSize(void*,int,int)" export func SDL_RenderPresent(renderer) - native ffi "SDL2:SDL_RenderPresent:void:void*" + native ffi "SDL2" "void SDL_RenderPresent(void*)" export func SDL_SetRenderDrawColor(renderer, r, g, b, a) - native ffi "SDL2:SDL_SetRenderDrawColor:int:void*,unsigned int,unsigned int,unsigned int,unsigned int" + native ffi "SDL2" "int SDL_SetRenderDrawColor(void*, unsigned int, unsigned int, unsigned int, unsigned int)" export func SDL_RenderClear(renderer) - native ffi "SDL2:SDL_RenderClear:int:void*" + native ffi "SDL2" "int SDL_RenderClear(void*)" export func SDL_RenderDrawLine(renderer, x1, y1, x2, y2) - native ffi "SDL2:SDL_RenderDrawLine:int:void*,int,int,int,int" + native ffi "SDL2" "int SDL_RenderDrawLine(void*,int,int,int,int)" // SDL_hints.h export let SDL_HINT_RENDER_LOGICAL_SIZE_MODE = "SDL_RENDER_LOGICAL_SIZE_MODE" export func SDL_SetHint(name, value) - native ffi "SDL2:SDL_SetHint:int:char*,char*" + native ffi "SDL2" "int SDL_SetHint(char*,char*)" diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiFunctionDefinition.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiFunctionDefinition.kt index 92caadf..176f618 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiFunctionDefinition.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiFunctionDefinition.kt @@ -7,21 +7,30 @@ class FfiFunctionDefinition( val parameters: List ) { companion object { - fun parse(def: String): FfiFunctionDefinition { - val parts = def.split(":", limit = 4) - if (parts.size !in arrayOf(3, 4) || parts.any { it.trim().isEmpty() }) { + fun parse(library: String, def: String): FfiFunctionDefinition { + fun invalid(): Nothing { throw RuntimeException( "FFI function definition is invalid, " + - "accepted format is 'library:function:return-type:(optional)parameters' " + - "but '${def}' was specified") + "accepted format is 'return-type function-name(parameter, parameter...)' " + + "but '${def}' was specified") } - val (library, function, returnType) = parts - val parametersString = if (parts.size == 4) parts[3] else "" + + val parts = def.split(" ", limit = 2) + if (parts.size != 2) { + invalid() + } + val (returnType, functionNameAndParameters) = parts + var (functionName, parametersAndClosingParentheses) = functionNameAndParameters.split("(", limit = 2) + parametersAndClosingParentheses = parametersAndClosingParentheses.trim() + if (!parametersAndClosingParentheses.endsWith(")")) { + invalid() + } + val parameterString = parametersAndClosingParentheses.substring(0, parametersAndClosingParentheses.length - 1) return FfiFunctionDefinition( library, - function, + functionName, returnType, - parametersString.split(",") + parameterString.split(",").map { it.trim() } ) } } diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaAutogen.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaAutogen.kt index 0a4c1c5..d7f0b49 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaAutogen.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaAutogen.kt @@ -155,7 +155,7 @@ class JavaAutogen(val javaClass: Class<*>) { ) private fun asNative(functionDefinition: JavaFunctionDefinition): Native = - Native(Symbol("java"), StringLiteral(functionDefinition.encode())) + Native(Symbol("java"), functionDefinition.encode().map { StringLiteral(it) }) private fun discriminate(parameter: Parameter): String = parameter.type.simpleName.lowercase().replace("[]", "_array") 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 68caef8..23b153d 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaFunctionDefinition.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaFunctionDefinition.kt @@ -8,25 +8,25 @@ class JavaFunctionDefinition( val parameters: List ) { companion object { - fun parse(def: String): JavaFunctionDefinition { - val parts = def.split(":", limit = 5) - if (!(parts.size == 4 || parts.size == 5) || parts.any { it.trim().isEmpty() }) { + fun parse(defs: List): JavaFunctionDefinition { + if (defs.size != 4 && defs.size != 5) { throw RuntimeException( "Java function definition is invalid, " + "accepted format is 'type:kind:symbol:return-type:(optional)parameters' " + - "but '${def}' was specified") + "but ${defs.joinToString(" ", prefix = "\"", postfix = "\"")} was specified") } - val (type, kind, symbol, returnType) = parts - val parameters = if (parts.size > 4) parts[4].split(",") else emptyList() + val (type, kind, symbol, returnType) = defs + val parameterString = if (defs.size == 5) defs[4] else "" + val parameters = if (parameterString.isNotEmpty()) parameterString.split(",") else emptyList() return JavaFunctionDefinition(type, kind, symbol, returnType, parameters) } } - fun encode(): String = buildString { - append("${type}:${kind}:${symbol}:${returnType}") - if (parameters.isNotEmpty()) { - append(":") - append(parameters.joinToString(",")) - } - } + fun encode(): List = listOf( + type, + kind, + symbol, + returnType, + parameters.joinToString(",") + ) } diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaNativeProvider.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaNativeProvider.kt index f929f71..ba9e461 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaNativeProvider.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaNativeProvider.kt @@ -10,8 +10,8 @@ import java.lang.invoke.MethodType class JavaNativeProvider : NativeProvider { private val lookup = MethodHandles.lookup() - override fun provideNativeFunction(definition: String, arguments: List): CallableFunction { - val functionDefinition = JavaFunctionDefinition.parse(definition) + override fun provideNativeFunction(definitions: List, arguments: List): CallableFunction { + val functionDefinition = JavaFunctionDefinition.parse(definitions) val javaClass = lookupClass(functionDefinition.type) val returnTypeClass = lookupClass(functionDefinition.returnType) val parameterClasses = functionDefinition.parameters.map { lookupClass(it) } diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JnaNativeProvider.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JnaNativeProvider.kt index cb3ad17..8413d76 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JnaNativeProvider.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JnaNativeProvider.kt @@ -8,8 +8,8 @@ import gay.pizza.pork.evaluator.NativeProvider import gay.pizza.pork.evaluator.None class JnaNativeProvider : NativeProvider { - override fun provideNativeFunction(definition: String, arguments: List): CallableFunction { - val functionDefinition = FfiFunctionDefinition.parse(definition) + override fun provideNativeFunction(definitions: List, arguments: List): CallableFunction { + val functionDefinition = FfiFunctionDefinition.parse(definitions[0], definitions[1]) val library = NativeLibrary.getInstance(functionDefinition.library) val function = library.getFunction(functionDefinition.function) ?: throw RuntimeException("Failed to find function ${functionDefinition.function} in library ${functionDefinition.library}") diff --git a/parser/src/main/kotlin/gay/pizza/pork/parser/Parser.kt b/parser/src/main/kotlin/gay/pizza/pork/parser/Parser.kt index 90e1bf1..31ba638 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/Parser.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/Parser.kt @@ -308,8 +308,11 @@ class Parser(source: TokenSource, attribution: NodeAttribution) : override fun parseNative(): Native = guarded(NodeType.Native) { expect(TokenType.Native) val form = parseSymbol() - val definition = parseStringLiteral() - Native(form, definition) + val definitions = mutableListOf() + while (peek(TokenType.StringLiteral)) { + definitions.add(parseStringLiteral()) + } + Native(form, definitions) } override fun parseNoneLiteral(): NoneLiteral = guarded(NodeType.NoneLiteral) { 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 5d750e4..2eb37a3 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/Printer.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/Printer.kt @@ -82,7 +82,12 @@ class Printer(buffer: StringBuilder) : NodeVisitor { append("native ") visit(node.form) append(" ") - visit(node.definition) + for ((index, argument) in node.definitions.withIndex()) { + visit(argument) + if (index + 1 != node.definitions.size) { + append(" ") + } + } } override fun visitNoneLiteral(node: NoneLiteral) { diff --git a/stdlib/src/main/pork/ffi/malloc.pork b/stdlib/src/main/pork/ffi/malloc.pork index 4e955fc..a6cc1fd 100644 --- a/stdlib/src/main/pork/ffi/malloc.pork +++ b/stdlib/src/main/pork/ffi/malloc.pork @@ -1,8 +1,8 @@ export func malloc(size) - native ffi "c:malloc:void*:size_t" + native ffi "c" "void* malloc(size_t)" export func calloc(size, count) - native ffi "c:calloc:void*:size_t,size_t" + native ffi "c" "void* calloc(size_t, size_t)" export func free(pointer) - native ffi "c:free:void:void*" + native ffi "c" "void free(void*)"