mirror of
https://github.com/GayPizzaSpecifications/pork.git
synced 2025-08-02 21:00:56 +00:00
pork: it's got it all, ffi, state machine tokenizer, and better IDE support
This commit is contained in:
parent
d355fb3914
commit
5078f38f61
@ -178,13 +178,20 @@ types:
|
|||||||
type: Block
|
type: Block
|
||||||
- name: elseBlock
|
- name: elseBlock
|
||||||
type: Block?
|
type: Block?
|
||||||
|
ImportPath:
|
||||||
|
parent: Node
|
||||||
|
referencedElementValue: components
|
||||||
|
referencedElementType: CompilationUnit
|
||||||
|
values:
|
||||||
|
- name: components
|
||||||
|
type: List<Symbol>
|
||||||
ImportDeclaration:
|
ImportDeclaration:
|
||||||
parent: Declaration
|
parent: Declaration
|
||||||
values:
|
values:
|
||||||
- name: form
|
- name: form
|
||||||
type: Symbol
|
type: Symbol
|
||||||
- name: components
|
- name: path
|
||||||
type: List<Symbol>
|
type: ImportPath
|
||||||
IntegerLiteral:
|
IntegerLiteral:
|
||||||
parent: Expression
|
parent: Expression
|
||||||
values:
|
values:
|
||||||
|
@ -18,6 +18,7 @@ digraph A {
|
|||||||
type_FunctionDefinition [shape=box,label="FunctionDefinition"]
|
type_FunctionDefinition [shape=box,label="FunctionDefinition"]
|
||||||
type_LetDefinition [shape=box,label="LetDefinition"]
|
type_LetDefinition [shape=box,label="LetDefinition"]
|
||||||
type_If [shape=box,label="If"]
|
type_If [shape=box,label="If"]
|
||||||
|
type_ImportPath [shape=box,label="ImportPath"]
|
||||||
type_ImportDeclaration [shape=box,label="ImportDeclaration"]
|
type_ImportDeclaration [shape=box,label="ImportDeclaration"]
|
||||||
type_IntegerLiteral [shape=box,label="IntegerLiteral"]
|
type_IntegerLiteral [shape=box,label="IntegerLiteral"]
|
||||||
type_LongLiteral [shape=box,label="LongLiteral"]
|
type_LongLiteral [shape=box,label="LongLiteral"]
|
||||||
@ -45,6 +46,7 @@ digraph A {
|
|||||||
type_Node -> type_Block
|
type_Node -> type_Block
|
||||||
type_Node -> type_CompilationUnit
|
type_Node -> type_CompilationUnit
|
||||||
type_Node -> type_ArgumentSpec
|
type_Node -> type_ArgumentSpec
|
||||||
|
type_Node -> type_ImportPath
|
||||||
type_Node -> type_ForInItem
|
type_Node -> type_ForInItem
|
||||||
type_Node -> type_Native
|
type_Node -> type_Native
|
||||||
type_Expression -> type_LetAssignment
|
type_Expression -> type_LetAssignment
|
||||||
@ -98,7 +100,9 @@ digraph A {
|
|||||||
type_LetDefinition -> type_Expression [style=dotted]
|
type_LetDefinition -> type_Expression [style=dotted]
|
||||||
type_If -> type_Expression [style=dotted]
|
type_If -> type_Expression [style=dotted]
|
||||||
type_If -> type_Block [style=dotted]
|
type_If -> type_Block [style=dotted]
|
||||||
|
type_ImportPath -> type_Symbol [style=dotted]
|
||||||
type_ImportDeclaration -> type_Symbol [style=dotted]
|
type_ImportDeclaration -> type_Symbol [style=dotted]
|
||||||
|
type_ImportDeclaration -> type_ImportPath [style=dotted]
|
||||||
type_ListLiteral -> type_Expression [style=dotted]
|
type_ListLiteral -> type_Expression [style=dotted]
|
||||||
type_Parentheses -> type_Expression [style=dotted]
|
type_Parentheses -> type_Expression [style=dotted]
|
||||||
type_PrefixOperation -> type_PrefixOperator [style=dotted]
|
type_PrefixOperation -> type_PrefixOperator [style=dotted]
|
||||||
|
@ -6,23 +6,23 @@ import kotlinx.serialization.Serializable
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("importDeclaration")
|
@SerialName("importDeclaration")
|
||||||
class ImportDeclaration(val form: Symbol, val components: List<Symbol>) : Declaration() {
|
class ImportDeclaration(val form: Symbol, val path: ImportPath) : Declaration() {
|
||||||
override val type: NodeType = NodeType.ImportDeclaration
|
override val type: NodeType = NodeType.ImportDeclaration
|
||||||
|
|
||||||
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
|
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
|
||||||
visitor.visitAll(listOf(form), components)
|
visitor.visitNodes(form, path)
|
||||||
|
|
||||||
override fun <T> visit(visitor: NodeVisitor<T>): T =
|
override fun <T> visit(visitor: NodeVisitor<T>): T =
|
||||||
visitor.visitImportDeclaration(this)
|
visitor.visitImportDeclaration(this)
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (other !is ImportDeclaration) return false
|
if (other !is ImportDeclaration) return false
|
||||||
return other.form == form && other.components == components
|
return other.form == form && other.path == path
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
var result = form.hashCode()
|
var result = form.hashCode()
|
||||||
result = 31 * result + components.hashCode()
|
result = 31 * result + path.hashCode()
|
||||||
result = 31 * result + type.hashCode()
|
result = 31 * result + type.hashCode()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
28
ast/src/main/kotlin/gay/pizza/pork/ast/gen/ImportPath.kt
Normal file
28
ast/src/main/kotlin/gay/pizza/pork/ast/gen/ImportPath.kt
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// GENERATED CODE FROM PORK AST CODEGEN
|
||||||
|
package gay.pizza.pork.ast.gen
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@SerialName("importPath")
|
||||||
|
class ImportPath(val components: List<Symbol>) : Node() {
|
||||||
|
override val type: NodeType = NodeType.ImportPath
|
||||||
|
|
||||||
|
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
|
||||||
|
visitor.visitAll(components)
|
||||||
|
|
||||||
|
override fun <T> visit(visitor: NodeVisitor<T>): T =
|
||||||
|
visitor.visitImportPath(this)
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other !is ImportPath) return false
|
||||||
|
return other.components == components
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = components.hashCode()
|
||||||
|
result = 31 * result + type.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
@ -41,6 +41,9 @@ class NodeCoalescer(val followChildren: Boolean = true, val handler: (Node) -> U
|
|||||||
override fun visitImportDeclaration(node: ImportDeclaration): Unit =
|
override fun visitImportDeclaration(node: ImportDeclaration): Unit =
|
||||||
handle(node)
|
handle(node)
|
||||||
|
|
||||||
|
override fun visitImportPath(node: ImportPath): Unit =
|
||||||
|
handle(node)
|
||||||
|
|
||||||
override fun visitIndexedBy(node: IndexedBy): Unit =
|
override fun visitIndexedBy(node: IndexedBy): Unit =
|
||||||
handle(node)
|
handle(node)
|
||||||
|
|
||||||
|
@ -34,6 +34,8 @@ interface NodeParser {
|
|||||||
|
|
||||||
fun parseImportDeclaration(): ImportDeclaration
|
fun parseImportDeclaration(): ImportDeclaration
|
||||||
|
|
||||||
|
fun parseImportPath(): ImportPath
|
||||||
|
|
||||||
fun parseIndexedBy(): IndexedBy
|
fun parseIndexedBy(): IndexedBy
|
||||||
|
|
||||||
fun parseInfixOperation(): InfixOperation
|
fun parseInfixOperation(): InfixOperation
|
||||||
|
@ -19,6 +19,7 @@ fun NodeParser.parse(type: NodeType): Node =
|
|||||||
NodeType.FunctionDefinition -> parseFunctionDefinition()
|
NodeType.FunctionDefinition -> parseFunctionDefinition()
|
||||||
NodeType.LetDefinition -> parseLetDefinition()
|
NodeType.LetDefinition -> parseLetDefinition()
|
||||||
NodeType.If -> parseIf()
|
NodeType.If -> parseIf()
|
||||||
|
NodeType.ImportPath -> parseImportPath()
|
||||||
NodeType.ImportDeclaration -> parseImportDeclaration()
|
NodeType.ImportDeclaration -> parseImportDeclaration()
|
||||||
NodeType.IntegerLiteral -> parseIntegerLiteral()
|
NodeType.IntegerLiteral -> parseIntegerLiteral()
|
||||||
NodeType.LongLiteral -> parseLongLiteral()
|
NodeType.LongLiteral -> parseLongLiteral()
|
||||||
|
@ -19,6 +19,7 @@ enum class NodeType(val parent: NodeType? = null) {
|
|||||||
FunctionDefinition(Definition),
|
FunctionDefinition(Definition),
|
||||||
If(Expression),
|
If(Expression),
|
||||||
ImportDeclaration(Declaration),
|
ImportDeclaration(Declaration),
|
||||||
|
ImportPath(Node),
|
||||||
IndexedBy(Expression),
|
IndexedBy(Expression),
|
||||||
InfixOperation(Expression),
|
InfixOperation(Expression),
|
||||||
IntegerLiteral(Expression),
|
IntegerLiteral(Expression),
|
||||||
|
@ -28,6 +28,8 @@ interface NodeVisitor<T> {
|
|||||||
|
|
||||||
fun visitImportDeclaration(node: ImportDeclaration): T
|
fun visitImportDeclaration(node: ImportDeclaration): T
|
||||||
|
|
||||||
|
fun visitImportPath(node: ImportPath): T
|
||||||
|
|
||||||
fun visitIndexedBy(node: IndexedBy): T
|
fun visitIndexedBy(node: IndexedBy): T
|
||||||
|
|
||||||
fun visitInfixOperation(node: InfixOperation): T
|
fun visitInfixOperation(node: InfixOperation): T
|
||||||
|
@ -16,6 +16,7 @@ fun <T> NodeVisitor<T>.visit(node: Node): T =
|
|||||||
is FunctionDefinition -> visitFunctionDefinition(node)
|
is FunctionDefinition -> visitFunctionDefinition(node)
|
||||||
is LetDefinition -> visitLetDefinition(node)
|
is LetDefinition -> visitLetDefinition(node)
|
||||||
is If -> visitIf(node)
|
is If -> visitIf(node)
|
||||||
|
is ImportPath -> visitImportPath(node)
|
||||||
is ImportDeclaration -> visitImportDeclaration(node)
|
is ImportDeclaration -> visitImportDeclaration(node)
|
||||||
is IntegerLiteral -> visitIntegerLiteral(node)
|
is IntegerLiteral -> visitIntegerLiteral(node)
|
||||||
is LongLiteral -> visitLongLiteral(node)
|
is LongLiteral -> visitLongLiteral(node)
|
||||||
|
@ -54,7 +54,7 @@ class CompilationUnitContext(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun processImport(import: ImportDeclaration) {
|
private fun processImport(import: ImportDeclaration) {
|
||||||
val importPath = import.components.joinToString("/") { it.id } + ".pork"
|
val importPath = import.path.components.joinToString("/") { it.id } + ".pork"
|
||||||
val importLocator = ImportLocator(import.form.id, importPath)
|
val importLocator = ImportLocator(import.form.id, importPath)
|
||||||
val evaluationContext = evaluator.context(importLocator)
|
val evaluationContext = evaluator.context(importLocator)
|
||||||
internalScope.inherit(evaluationContext.externalScope)
|
internalScope.inherit(evaluationContext.externalScope)
|
||||||
@ -67,9 +67,11 @@ class CompilationUnitContext(
|
|||||||
companion object {
|
companion object {
|
||||||
private val preludeImport = ImportDeclaration(
|
private val preludeImport = ImportDeclaration(
|
||||||
Symbol("std"),
|
Symbol("std"),
|
||||||
listOf(
|
ImportPath(
|
||||||
Symbol("lang"),
|
listOf(
|
||||||
Symbol("prelude")
|
Symbol("lang"),
|
||||||
|
Symbol("prelude")
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -391,6 +391,10 @@ class EvaluationVisitor(root: Scope, val stack: CallStack) : NodeVisitor<Any> {
|
|||||||
topLevelUsedError("ImportDeclaration", "CompilationUnitContext")
|
topLevelUsedError("ImportDeclaration", "CompilationUnitContext")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun visitImportPath(node: ImportPath): Any {
|
||||||
|
topLevelUsedError("ImportPath", "CompilationUnitContext")
|
||||||
|
}
|
||||||
|
|
||||||
override fun visitIndexedBy(node: IndexedBy): Any {
|
override fun visitIndexedBy(node: IndexedBy): Any {
|
||||||
val value = node.expression.visit(this)
|
val value = node.expression.visit(this)
|
||||||
val index = node.index.visit(this)
|
val index = node.index.visit(this)
|
||||||
|
@ -11,7 +11,7 @@ class FunctionContext(val compilationUnitContext: CompilationUnitContext, val no
|
|||||||
val native = node.native!!
|
val native = node.native!!
|
||||||
val nativeFunctionProvider =
|
val nativeFunctionProvider =
|
||||||
compilationUnitContext.evaluator.nativeFunctionProvider(native.form.id)
|
compilationUnitContext.evaluator.nativeFunctionProvider(native.form.id)
|
||||||
nativeFunctionProvider.provideNativeFunction(native.definitions.map { it.text }, node.arguments)
|
nativeFunctionProvider.provideNativeFunction(native.definitions.map { it.text }, node.arguments, compilationUnitContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val nativeCached by lazy { resolveMaybeNative() }
|
private val nativeCached by lazy { resolveMaybeNative() }
|
||||||
|
@ -11,7 +11,15 @@ class InternalNativeProvider(val quiet: Boolean = false) : NativeProvider {
|
|||||||
"listInitWith" to CallableFunction(::listInitWith)
|
"listInitWith" to CallableFunction(::listInitWith)
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun provideNativeFunction(definitions: List<String>, arguments: List<ArgumentSpec>): CallableFunction {
|
fun add(name: String, function: CallableFunction) {
|
||||||
|
functions[name] = function
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun provideNativeFunction(
|
||||||
|
definitions: List<String>,
|
||||||
|
arguments: List<ArgumentSpec>,
|
||||||
|
inside: CompilationUnitContext
|
||||||
|
): CallableFunction {
|
||||||
val definition = definitions[0]
|
val definition = definitions[0]
|
||||||
return functions[definition] ?: throw RuntimeException("Unknown Internal Function: $definition")
|
return functions[definition] ?: throw RuntimeException("Unknown Internal Function: $definition")
|
||||||
}
|
}
|
||||||
|
@ -3,5 +3,5 @@ package gay.pizza.pork.evaluator
|
|||||||
import gay.pizza.pork.ast.gen.ArgumentSpec
|
import gay.pizza.pork.ast.gen.ArgumentSpec
|
||||||
|
|
||||||
interface NativeProvider {
|
interface NativeProvider {
|
||||||
fun provideNativeFunction(definitions: List<String>, arguments: List<ArgumentSpec>): CallableFunction
|
fun provideNativeFunction(definitions: List<String>, arguments: List<ArgumentSpec>, inside: CompilationUnitContext): CallableFunction
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,23 @@
|
|||||||
import std ffi.malloc
|
import std ffi.struct
|
||||||
|
|
||||||
|
export let timeval = ffiStructDefine(
|
||||||
|
"long", "seconds",
|
||||||
|
"unsigned int", "microseconds"
|
||||||
|
)
|
||||||
|
|
||||||
|
export let timezone = ffiStructDefine(
|
||||||
|
"int", "minutes_greenwich",
|
||||||
|
"int", "dst_time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func gettimeofday(value, tz)
|
||||||
|
native ffi "c" "int gettimeofday(struct timeval*, struct timezone*)"
|
||||||
|
|
||||||
export func main() {
|
export func main() {
|
||||||
let pointer = malloc(8192)
|
let time = ffiStructAllocate(timeval)
|
||||||
println(pointer)
|
let zone = ffiStructAllocate(timezone)
|
||||||
free(pointer)
|
let result = gettimeofday(time, zone)
|
||||||
|
let seconds = ffiStructValue(timeval, "seconds", time)
|
||||||
|
println("Result:", result)
|
||||||
|
println("Seconds:", seconds)
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,9 @@ export func SDL_GetModState()
|
|||||||
|
|
||||||
export let SDL_RENDERER_PRESENTVSYNC = 4
|
export let SDL_RENDERER_PRESENTVSYNC = 4
|
||||||
|
|
||||||
|
export func SDL_PollEvent(event)
|
||||||
|
native ffi "SDL2" "int SDL_PollEvent(struct SDL_Event*)"
|
||||||
|
|
||||||
export func SDL_CreateRenderer(window, index, flags)
|
export func SDL_CreateRenderer(window, index, flags)
|
||||||
native ffi "SDL2" "void* SDL_CreateRenderer(void*, int, unsigned int)"
|
native ffi "SDL2" "void* SDL_CreateRenderer(void*, int, unsigned int)"
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package gay.pizza.pork.ffi
|
package gay.pizza.pork.ffi
|
||||||
|
|
||||||
import java.nio.file.Path
|
|
||||||
import kotlin.io.path.*
|
import kotlin.io.path.*
|
||||||
|
|
||||||
object FfiMacPlatform : FfiPlatform {
|
object FfiMacPlatform : FfiPlatform {
|
||||||
@ -8,18 +7,23 @@ object FfiMacPlatform : FfiPlatform {
|
|||||||
"/Library/Frameworks"
|
"/Library/Frameworks"
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun findLibrary(name: String): Path? {
|
override fun findLibrary(name: String): String? {
|
||||||
val frameworksToCheck = frameworksDirectories.map { frameworkDirectory ->
|
val frameworksToCheck = frameworksDirectories.map { frameworkDirectory ->
|
||||||
Path("$frameworkDirectory/$name.framework/$name")
|
Path("$frameworkDirectory/$name.framework/$name")
|
||||||
}
|
}
|
||||||
for (framework in frameworksToCheck) {
|
for (framework in frameworksToCheck) {
|
||||||
if (!framework.exists()) continue
|
if (!framework.exists()) continue
|
||||||
return if (framework.isSymbolicLink()) {
|
return if (framework.isSymbolicLink()) {
|
||||||
return framework.parent.resolve(framework.readSymbolicLink()).absolute()
|
return framework.parent.resolve(framework.readSymbolicLink()).absolutePathString()
|
||||||
} else {
|
} else {
|
||||||
framework.absolute()
|
framework.absolutePathString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name == "c") {
|
||||||
|
return "libSystem.dylib"
|
||||||
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,104 +3,153 @@ package gay.pizza.pork.ffi
|
|||||||
import com.kenai.jffi.*
|
import com.kenai.jffi.*
|
||||||
import com.kenai.jffi.Function
|
import com.kenai.jffi.Function
|
||||||
import gay.pizza.pork.ast.gen.ArgumentSpec
|
import gay.pizza.pork.ast.gen.ArgumentSpec
|
||||||
import gay.pizza.pork.evaluator.CallableFunction
|
import gay.pizza.pork.evaluator.*
|
||||||
import gay.pizza.pork.evaluator.NativeProvider
|
|
||||||
import gay.pizza.pork.evaluator.None
|
|
||||||
import java.nio.file.Path
|
|
||||||
import kotlin.io.path.Path
|
import kotlin.io.path.Path
|
||||||
import kotlin.io.path.absolutePathString
|
import kotlin.io.path.absolutePathString
|
||||||
import kotlin.io.path.exists
|
import kotlin.io.path.exists
|
||||||
|
|
||||||
class FfiNativeProvider : NativeProvider {
|
class FfiNativeProvider : NativeProvider {
|
||||||
private val ffiTypeRegistry = FfiTypeRegistry()
|
private val internalFunctions = mutableMapOf<String, (ArgumentList) -> Any>(
|
||||||
|
"ffiStructDefine" to ::ffiStructDefine,
|
||||||
|
"ffiStructAllocate" to ::ffiStructAllocate,
|
||||||
|
"ffiStructValue" to ::ffiStructValue,
|
||||||
|
"ffiStructBytes" to ::ffiStructBytes
|
||||||
|
)
|
||||||
|
|
||||||
|
private val rootTypeRegistry = FfiTypeRegistry()
|
||||||
|
|
||||||
|
init {
|
||||||
|
rootTypeRegistry.registerPrimitiveTypes()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun provideNativeFunction(
|
||||||
|
definitions: List<String>,
|
||||||
|
arguments: List<ArgumentSpec>,
|
||||||
|
inside: CompilationUnitContext
|
||||||
|
): CallableFunction {
|
||||||
|
if (definitions[0] == "internal") {
|
||||||
|
val internal = internalFunctions[definitions[1]] ?:
|
||||||
|
throw RuntimeException("Unknown internal function: ${definitions[1]}")
|
||||||
|
return CallableFunction { functionArguments, _ ->
|
||||||
|
internal(functionArguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun provideNativeFunction(definitions: List<String>, arguments: List<ArgumentSpec>): CallableFunction {
|
|
||||||
val functionDefinition = FfiFunctionDefinition.parse(definitions[0], definitions[1])
|
val functionDefinition = FfiFunctionDefinition.parse(definitions[0], definitions[1])
|
||||||
val functionAddress = lookupSymbol(functionDefinition)
|
val functionAddress = lookupSymbol(functionDefinition)
|
||||||
|
|
||||||
|
val ffiTypeRegistry = rootTypeRegistry.fork()
|
||||||
|
|
||||||
|
addStructDefs(ffiTypeRegistry, functionDefinition.parameters, inside)
|
||||||
|
|
||||||
val parameters = functionDefinition.parameters.map { id ->
|
val parameters = functionDefinition.parameters.map { id ->
|
||||||
ffiTypeRegistry.lookup(id) ?: throw RuntimeException("Unknown ffi type: $id")
|
ffiTypeRegistry.required(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
val returnTypeId = functionDefinition.returnType
|
val returnTypeId = functionDefinition.returnType
|
||||||
val returnType = ffiTypeRegistry.lookup(returnTypeId) ?:
|
val returnType = ffiTypeRegistry.required(returnTypeId)
|
||||||
throw RuntimeException("Unknown ffi return type: $returnTypeId")
|
|
||||||
val returnTypeFfi = typeConversion(returnType)
|
val returnTypeFfi = typeConversion(returnType)
|
||||||
val parameterArray = parameters.map { typeConversion(it) }.toTypedArray()
|
val parameterArray = parameters.map { typeConversion(it) }.toTypedArray()
|
||||||
val function = Function(functionAddress, returnTypeFfi, *parameterArray)
|
val function = Function(functionAddress, returnTypeFfi, *parameterArray)
|
||||||
val context = function.callContext
|
val context = function.callContext
|
||||||
val invoker = Invoker.getInstance()
|
val invoker = Invoker.getInstance()
|
||||||
return CallableFunction { functionArguments, _ ->
|
return CallableFunction { functionArguments, _ ->
|
||||||
val buffer = HeapInvocationBuffer(context)
|
|
||||||
val freeStringList = mutableListOf<FfiString>()
|
val freeStringList = mutableListOf<FfiString>()
|
||||||
for ((index, spec) in arguments.withIndex()) {
|
|
||||||
val ffiType = ffiTypeRegistry.lookup(functionDefinition.parameters[index]) ?:
|
|
||||||
throw RuntimeException("Unknown ffi type: ${functionDefinition.parameters[index]}")
|
|
||||||
if (spec.multiple) {
|
|
||||||
val variableArguments = functionArguments
|
|
||||||
.subList(index, functionArguments.size)
|
|
||||||
variableArguments.forEach {
|
|
||||||
var value = it
|
|
||||||
if (value is String) {
|
|
||||||
value = FfiString.allocate(value)
|
|
||||||
freeStringList.add(value)
|
|
||||||
}
|
|
||||||
FfiPrimitiveType.push(buffer, value)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
var argumentValue = functionArguments[index]
|
|
||||||
if (argumentValue is String) {
|
|
||||||
argumentValue = FfiString.allocate(argumentValue)
|
|
||||||
freeStringList.add(argumentValue)
|
|
||||||
}
|
|
||||||
ffiType.put(buffer, argumentValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
val buffer = buildArgumentList(
|
||||||
|
context,
|
||||||
|
arguments,
|
||||||
|
functionArguments,
|
||||||
|
ffiTypeRegistry,
|
||||||
|
functionDefinition,
|
||||||
|
freeStringList
|
||||||
|
)
|
||||||
return@CallableFunction invoke(invoker, function, buffer, returnType)
|
return@CallableFunction invoke(invoker, function, buffer, returnType)
|
||||||
} finally {
|
} finally {
|
||||||
freeStringList.forEach { it.free() }
|
for (ffiString in freeStringList) {
|
||||||
|
ffiString.free()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun addStructDefs(ffiTypeRegistry: FfiTypeRegistry, types: List<String>, inside: CompilationUnitContext) {
|
||||||
|
for (parameter in types) {
|
||||||
|
if (!parameter.startsWith("struct ")) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var structureName = parameter.substring(7)
|
||||||
|
if (structureName.endsWith("*")) {
|
||||||
|
structureName = structureName.substring(0, structureName.length - 1)
|
||||||
|
}
|
||||||
|
val structureDefinitionValue = inside.internalScope.value(structureName)
|
||||||
|
if (structureDefinitionValue !is FfiStructDefinition) {
|
||||||
|
throw RuntimeException("Structure '${structureName}' was not an FfiStructDefinition.")
|
||||||
|
}
|
||||||
|
val struct = FfiStruct(ffiTypeRegistry)
|
||||||
|
for ((name, type) in structureDefinitionValue.values) {
|
||||||
|
struct.add(name, type)
|
||||||
|
}
|
||||||
|
ffiTypeRegistry.add("struct $structureName", struct)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildArgumentList(
|
||||||
|
context: CallContext,
|
||||||
|
functionArgumentSpecs: List<ArgumentSpec>,
|
||||||
|
functionArguments: List<Any>,
|
||||||
|
ffiTypeRegistry: FfiTypeRegistry,
|
||||||
|
functionDefinition: FfiFunctionDefinition,
|
||||||
|
freeStringList: MutableList<FfiString>
|
||||||
|
): HeapInvocationBuffer {
|
||||||
|
val buffer = HeapInvocationBuffer(context)
|
||||||
|
for ((index, spec) in functionArgumentSpecs.withIndex()) {
|
||||||
|
val ffiType = ffiTypeRegistry.lookup(functionDefinition.parameters[index]) ?:
|
||||||
|
throw RuntimeException("Unknown ffi type: ${functionDefinition.parameters[index]}")
|
||||||
|
if (spec.multiple) {
|
||||||
|
val variableArguments = functionArguments
|
||||||
|
.subList(index, functionArguments.size)
|
||||||
|
for (variableArgument in variableArguments) {
|
||||||
|
var value = variableArgument
|
||||||
|
if (value is String) {
|
||||||
|
value = FfiString.allocate(value)
|
||||||
|
freeStringList.add(value)
|
||||||
|
}
|
||||||
|
FfiPrimitiveType.push(buffer, value)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
var argumentValue = functionArguments[index]
|
||||||
|
if (argumentValue is String) {
|
||||||
|
argumentValue = FfiString.allocate(argumentValue)
|
||||||
|
freeStringList.add(argumentValue)
|
||||||
|
}
|
||||||
|
ffiType.put(buffer, argumentValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buffer
|
||||||
|
}
|
||||||
|
|
||||||
private fun lookupSymbol(functionDefinition: FfiFunctionDefinition): Long {
|
private fun lookupSymbol(functionDefinition: FfiFunctionDefinition): Long {
|
||||||
val actualLibraryPath = findLibraryPath(functionDefinition.library)
|
val actualLibraryPath = findLibraryPath(functionDefinition.library)
|
||||||
val library = Library.getCachedInstance(actualLibraryPath.absolutePathString(), Library.NOW)
|
val library = Library.getCachedInstance(actualLibraryPath, Library.NOW)
|
||||||
?: throw RuntimeException("Failed to load library $actualLibraryPath")
|
?: throw RuntimeException("Failed to load library $actualLibraryPath")
|
||||||
val functionAddress = library.getSymbolAddress(functionDefinition.function)
|
val functionAddress = library.getSymbolAddress(functionDefinition.function)
|
||||||
if (functionAddress == 0L) {
|
if (functionAddress == 0L) {
|
||||||
throw RuntimeException(
|
throw RuntimeException(
|
||||||
"Failed to find symbol ${functionDefinition.function} in " +
|
"Failed to find symbol ${functionDefinition.function} in " +
|
||||||
"library ${actualLibraryPath.absolutePathString()}")
|
"library $actualLibraryPath")
|
||||||
}
|
}
|
||||||
return functionAddress
|
return functionAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun typeConversion(type: FfiType): Type = when (type) {
|
private fun findLibraryPath(name: String): String {
|
||||||
FfiPrimitiveType.UnsignedByte -> Type.UINT8
|
|
||||||
FfiPrimitiveType.Byte -> Type.SINT8
|
|
||||||
FfiPrimitiveType.UnsignedInt -> Type.UINT32
|
|
||||||
FfiPrimitiveType.Int -> Type.SINT32
|
|
||||||
FfiPrimitiveType.UnsignedShort -> Type.UINT16
|
|
||||||
FfiPrimitiveType.Short -> Type.SINT16
|
|
||||||
FfiPrimitiveType.UnsignedLong -> Type.UINT64
|
|
||||||
FfiPrimitiveType.Long -> Type.SINT64
|
|
||||||
FfiPrimitiveType.String -> Type.POINTER
|
|
||||||
FfiPrimitiveType.Pointer -> Type.POINTER
|
|
||||||
FfiPrimitiveType.Void -> Type.VOID
|
|
||||||
else -> throw RuntimeException("Unknown ffi type: $type")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findLibraryPath(name: String): Path {
|
|
||||||
val initialPath = Path(name)
|
val initialPath = Path(name)
|
||||||
if (initialPath.exists()) {
|
if (initialPath.exists()) {
|
||||||
return initialPath
|
return initialPath.absolutePathString()
|
||||||
}
|
}
|
||||||
return FfiPlatforms.current.platform.findLibrary(name)
|
return FfiPlatforms.current.platform.findLibrary(name) ?: name
|
||||||
?: throw RuntimeException("Unable to find library: $name")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun invoke(invoker: Invoker, function: Function, buffer: HeapInvocationBuffer, type: FfiType): Any = when (type) {
|
private fun invoke(invoker: Invoker, function: Function, buffer: HeapInvocationBuffer, type: FfiType): Any = when (type) {
|
||||||
@ -113,4 +162,65 @@ class FfiNativeProvider : NativeProvider {
|
|||||||
FfiPrimitiveType.String -> invoker.invokeAddress(function, buffer)
|
FfiPrimitiveType.String -> invoker.invokeAddress(function, buffer)
|
||||||
else -> throw RuntimeException("Unsupported ffi return type: $type")
|
else -> throw RuntimeException("Unsupported ffi return type: $type")
|
||||||
} ?: None
|
} ?: None
|
||||||
|
|
||||||
|
private fun ffiStructDefine(arguments: ArgumentList): Any {
|
||||||
|
val copy = arguments.toMutableList()
|
||||||
|
val fields = LinkedHashMap<String, String>()
|
||||||
|
while (copy.isNotEmpty()) {
|
||||||
|
val type = copy.removeAt(0)
|
||||||
|
fields[copy.removeAt(0).toString()] = type.toString()
|
||||||
|
}
|
||||||
|
return FfiStructDefinition(fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ffiStructAllocate(arguments: ArgumentList): Any {
|
||||||
|
val ffiTypeRegistry = rootTypeRegistry.fork()
|
||||||
|
val structDefinition = arguments[0] as FfiStructDefinition
|
||||||
|
val structType = FfiStruct(ffiTypeRegistry)
|
||||||
|
for ((name, type) in structDefinition.values) {
|
||||||
|
structType.add(name, type)
|
||||||
|
}
|
||||||
|
return FfiAddress(MemoryIO.getInstance().allocateMemory(structType.size, true))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ffiStructValue(arguments: ArgumentList): Any {
|
||||||
|
val structDefinition = arguments[0] as FfiStructDefinition
|
||||||
|
val field = arguments[1] as String
|
||||||
|
val value = arguments[2] as FfiAddress
|
||||||
|
val ffiTypeRegistry = rootTypeRegistry.fork()
|
||||||
|
val structType = FfiStruct(ffiTypeRegistry)
|
||||||
|
for ((name, type) in structDefinition.values) {
|
||||||
|
structType.add(name, type)
|
||||||
|
}
|
||||||
|
return structType.get(field, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ffiStructBytes(arguments: ArgumentList): Any {
|
||||||
|
val structDefinition = arguments[0] as FfiStructDefinition
|
||||||
|
val address = arguments[1] as FfiAddress
|
||||||
|
val ffiTypeRegistry = rootTypeRegistry.fork()
|
||||||
|
val structType = FfiStruct(ffiTypeRegistry)
|
||||||
|
for ((name, type) in structDefinition.values) {
|
||||||
|
structType.add(name, type)
|
||||||
|
}
|
||||||
|
return structType.read(address, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun typeConversion(type: FfiType): Type = when (type) {
|
||||||
|
FfiPrimitiveType.UnsignedByte -> Type.UINT8
|
||||||
|
FfiPrimitiveType.Byte -> Type.SINT8
|
||||||
|
FfiPrimitiveType.UnsignedInt -> Type.UINT32
|
||||||
|
FfiPrimitiveType.Int -> Type.SINT32
|
||||||
|
FfiPrimitiveType.UnsignedShort -> Type.UINT16
|
||||||
|
FfiPrimitiveType.Short -> Type.SINT16
|
||||||
|
FfiPrimitiveType.UnsignedLong -> Type.UINT64
|
||||||
|
FfiPrimitiveType.Long -> Type.SINT64
|
||||||
|
FfiPrimitiveType.String -> Type.POINTER
|
||||||
|
FfiPrimitiveType.Pointer -> Type.POINTER
|
||||||
|
FfiPrimitiveType.Void -> Type.VOID
|
||||||
|
is FfiStruct -> type.ffiStruct
|
||||||
|
else -> throw RuntimeException("Unknown ffi type: $type")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package gay.pizza.pork.ffi
|
package gay.pizza.pork.ffi
|
||||||
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
enum class FfiPlatforms(val id: String, val platform: FfiPlatform) {
|
enum class FfiPlatforms(val id: String, val platform: FfiPlatform) {
|
||||||
Mac("macOS", FfiMacPlatform),
|
Mac("macOS", FfiMacPlatform),
|
||||||
Windows("Windows", FfiWindowsPlatform),
|
Windows("Windows", FfiWindowsPlatform),
|
||||||
@ -20,5 +18,5 @@ enum class FfiPlatforms(val id: String, val platform: FfiPlatform) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface FfiPlatform {
|
interface FfiPlatform {
|
||||||
fun findLibrary(name: String): Path?
|
fun findLibrary(name: String): String?
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package gay.pizza.pork.ffi
|
package gay.pizza.pork.ffi
|
||||||
|
|
||||||
import com.kenai.jffi.InvocationBuffer
|
import com.kenai.jffi.InvocationBuffer
|
||||||
|
import com.kenai.jffi.MemoryIO
|
||||||
import gay.pizza.pork.evaluator.None
|
import gay.pizza.pork.evaluator.None
|
||||||
|
|
||||||
enum class FfiPrimitiveType(
|
enum class FfiPrimitiveType(
|
||||||
@ -89,6 +90,21 @@ enum class FfiPrimitiveType(
|
|||||||
return ffi
|
return ffi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun read(address: FfiAddress, offset: kotlin.Int): Any {
|
||||||
|
val actual = address.location + offset
|
||||||
|
return when (this) {
|
||||||
|
UnsignedByte, Byte -> MemoryIO.getInstance().getByte(actual)
|
||||||
|
UnsignedShort, Short -> MemoryIO.getInstance().getShort(actual)
|
||||||
|
UnsignedInt, Int -> MemoryIO.getInstance().getInt(actual)
|
||||||
|
UnsignedLong, Long -> MemoryIO.getInstance().getLong(actual)
|
||||||
|
Float -> MemoryIO.getInstance().getFloat(actual)
|
||||||
|
Double -> MemoryIO.getInstance().getDouble(actual)
|
||||||
|
Pointer -> MemoryIO.getInstance().getAddress(actual)
|
||||||
|
String -> FfiString(FfiAddress(actual))
|
||||||
|
Void -> None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun push(buffer: InvocationBuffer, value: Any): Unit = when (value) {
|
fun push(buffer: InvocationBuffer, value: Any): Unit = when (value) {
|
||||||
is kotlin.Byte -> buffer.putByte(value.toInt())
|
is kotlin.Byte -> buffer.putByte(value.toInt())
|
||||||
|
@ -1,36 +1,61 @@
|
|||||||
package gay.pizza.pork.ffi
|
package gay.pizza.pork.ffi
|
||||||
|
|
||||||
import com.kenai.jffi.InvocationBuffer
|
import com.kenai.jffi.InvocationBuffer
|
||||||
|
import com.kenai.jffi.MemoryIO
|
||||||
|
import com.kenai.jffi.Struct
|
||||||
import gay.pizza.pork.evaluator.None
|
import gay.pizza.pork.evaluator.None
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class FfiStruct : FfiType {
|
class FfiStruct(val ffiTypeRegistry: FfiTypeRegistry) : FfiType {
|
||||||
private val fields = TreeMap<String, FfiStructField>()
|
private val fields = LinkedHashMap<String, FfiStructField>()
|
||||||
|
private var internalStructType: Struct? = null
|
||||||
|
val ffiStruct: Struct
|
||||||
|
get() {
|
||||||
|
if (internalStructType == null) {
|
||||||
|
internalStructType = Struct.newStruct(*ffiTypes.map {
|
||||||
|
FfiNativeProvider.typeConversion(it)
|
||||||
|
}.toTypedArray())
|
||||||
|
}
|
||||||
|
return internalStructType!!
|
||||||
|
}
|
||||||
|
private var internalTypes: List<FfiType>? = null
|
||||||
|
val ffiTypes: List<FfiType>
|
||||||
|
get() {
|
||||||
|
if (internalTypes == null) {
|
||||||
|
internalTypes = fields.values.map { ffiTypeRegistry.required(it.type) }
|
||||||
|
}
|
||||||
|
return internalTypes!!
|
||||||
|
}
|
||||||
|
|
||||||
data class FfiStructField(val name: String, val type: FfiType)
|
data class FfiStructField(val name: String, val type: String)
|
||||||
|
|
||||||
fun add(field: String, type: FfiType) {
|
fun add(field: String, type: String) {
|
||||||
fields[field] = FfiStructField(field, type)
|
fields[field] = FfiStructField(field, type)
|
||||||
|
internalStructType = null
|
||||||
|
internalTypes = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override val size: Long
|
override val size: Long
|
||||||
get() = fields.values.sumOf { it.type.size }
|
get() = ffiStruct.size().toLong()
|
||||||
|
|
||||||
override fun put(buffer: InvocationBuffer, value: Any?) {
|
override fun put(buffer: InvocationBuffer, value: Any?) {
|
||||||
when (value) {
|
when (value) {
|
||||||
is Map<*, *> -> {
|
is Map<*, *> -> {
|
||||||
for (field in fields.values) {
|
for (field in fields.values) {
|
||||||
val item = value[field.name] ?: None
|
val item = value[field.name] ?: None
|
||||||
field.type.put(buffer, item)
|
val itemType = ffiTypeRegistry.required(field.type)
|
||||||
|
itemType.put(buffer, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is List<*> -> {
|
is List<*> -> {
|
||||||
for ((index, field) in fields.values.withIndex()) {
|
for ((index, field) in fields.values.withIndex()) {
|
||||||
field.type.put(buffer, value[index])
|
val itemType = ffiTypeRegistry.required(field.type)
|
||||||
|
itemType.put(buffer, value[index])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is None -> {}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
throw RuntimeException("Unknown value type: $value")
|
throw RuntimeException("Unknown value type: $value")
|
||||||
}
|
}
|
||||||
@ -40,4 +65,28 @@ class FfiStruct : FfiType {
|
|||||||
override fun value(ffi: Any?): Any {
|
override fun value(ffi: Any?): Any {
|
||||||
return None
|
return None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun read(address: FfiAddress, offset: Int): Any {
|
||||||
|
val bytes = ByteArray(size.toInt()) { 0 }
|
||||||
|
MemoryIO.getInstance().getByteArray(address.location, bytes, offset, size.toInt())
|
||||||
|
return bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
fun get(field: String, address: FfiAddress): Any {
|
||||||
|
var indexWithoutAlignment = 0L
|
||||||
|
var type: FfiType? = null
|
||||||
|
for ((index, key) in fields.keys.withIndex()) {
|
||||||
|
if (key == field) {
|
||||||
|
type = ffiTypes[index]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
indexWithoutAlignment += ffiTypes[index].size
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == null) {
|
||||||
|
throw RuntimeException("Unable to read unknown field $field from struct.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return type.read(address, indexWithoutAlignment.toInt())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
package gay.pizza.pork.ffi
|
||||||
|
|
||||||
|
data class FfiStructDefinition(val values: LinkedHashMap<String, String>)
|
@ -7,4 +7,5 @@ interface FfiType {
|
|||||||
|
|
||||||
fun put(buffer: InvocationBuffer, value: Any?)
|
fun put(buffer: InvocationBuffer, value: Any?)
|
||||||
fun value(ffi: Any?): Any
|
fun value(ffi: Any?): Any
|
||||||
|
fun read(address: FfiAddress, offset: Int): Any
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package gay.pizza.pork.ffi
|
package gay.pizza.pork.ffi
|
||||||
|
|
||||||
class FfiTypeRegistry {
|
class FfiTypeRegistry(val parent: FfiTypeRegistry? = null) {
|
||||||
private val types = mutableMapOf<String, FfiType>()
|
private val types = mutableMapOf<String, FfiType>()
|
||||||
|
|
||||||
init {
|
fun registerPrimitiveTypes() {
|
||||||
for (type in FfiPrimitiveType.entries) {
|
for (type in FfiPrimitiveType.entries) {
|
||||||
add(type.id, type)
|
add(type.id, type)
|
||||||
}
|
}
|
||||||
@ -12,7 +12,14 @@ class FfiTypeRegistry {
|
|||||||
|
|
||||||
fun add(name: String, type: FfiType) {
|
fun add(name: String, type: FfiType) {
|
||||||
types[name] = type
|
types[name] = type
|
||||||
|
|
||||||
|
if (type is FfiStruct) {
|
||||||
|
types["${name}*"] = FfiPrimitiveType.Pointer
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun lookup(name: String): FfiType? = types[name]
|
fun lookup(name: String): FfiType? = types[name] ?: parent?.lookup(name)
|
||||||
|
fun required(name: String): FfiType = lookup(name) ?: throw RuntimeException("Unknown ffi type: $name")
|
||||||
|
|
||||||
|
fun fork(): FfiTypeRegistry = FfiTypeRegistry(this)
|
||||||
}
|
}
|
||||||
|
@ -3,5 +3,5 @@ package gay.pizza.pork.ffi
|
|||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
object FfiUnixPlatform : FfiPlatform {
|
object FfiUnixPlatform : FfiPlatform {
|
||||||
override fun findLibrary(name: String): Path? = null
|
override fun findLibrary(name: String): String? = null
|
||||||
}
|
}
|
||||||
|
@ -3,5 +3,5 @@ package gay.pizza.pork.ffi
|
|||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
object FfiWindowsPlatform : FfiPlatform {
|
object FfiWindowsPlatform : FfiPlatform {
|
||||||
override fun findLibrary(name: String): Path? = null
|
override fun findLibrary(name: String): String? = null
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package gay.pizza.pork.ffi
|
|||||||
|
|
||||||
import gay.pizza.pork.ast.gen.ArgumentSpec
|
import gay.pizza.pork.ast.gen.ArgumentSpec
|
||||||
import gay.pizza.pork.evaluator.CallableFunction
|
import gay.pizza.pork.evaluator.CallableFunction
|
||||||
|
import gay.pizza.pork.evaluator.CompilationUnitContext
|
||||||
import gay.pizza.pork.evaluator.NativeProvider
|
import gay.pizza.pork.evaluator.NativeProvider
|
||||||
import gay.pizza.pork.evaluator.None
|
import gay.pizza.pork.evaluator.None
|
||||||
import java.lang.invoke.MethodHandles
|
import java.lang.invoke.MethodHandles
|
||||||
@ -10,7 +11,11 @@ import java.lang.invoke.MethodType
|
|||||||
class JavaNativeProvider : NativeProvider {
|
class JavaNativeProvider : NativeProvider {
|
||||||
private val lookup = MethodHandles.lookup()
|
private val lookup = MethodHandles.lookup()
|
||||||
|
|
||||||
override fun provideNativeFunction(definitions: List<String>, arguments: List<ArgumentSpec>): CallableFunction {
|
override fun provideNativeFunction(
|
||||||
|
definitions: List<String>,
|
||||||
|
arguments: List<ArgumentSpec>,
|
||||||
|
inside: CompilationUnitContext
|
||||||
|
): CallableFunction {
|
||||||
val functionDefinition = JavaFunctionDefinition.parse(definitions)
|
val functionDefinition = JavaFunctionDefinition.parse(definitions)
|
||||||
val javaClass = lookupClass(functionDefinition.type)
|
val javaClass = lookupClass(functionDefinition.type)
|
||||||
val returnTypeClass = lookupClass(functionDefinition.returnType)
|
val returnTypeClass = lookupClass(functionDefinition.returnType)
|
||||||
|
@ -23,7 +23,7 @@ class World(val importSource: ImportSource) {
|
|||||||
}
|
}
|
||||||
val charSource = contentSource.loadAsCharSource(importLocator.path)
|
val charSource = contentSource.loadAsCharSource(importLocator.path)
|
||||||
val tokenizer = Tokenizer(charSource)
|
val tokenizer = Tokenizer(charSource)
|
||||||
val tokenStream = tokenizer.tokenize()
|
val tokenStream = tokenizer.stream()
|
||||||
val parser = Parser(TokenStreamSource(tokenStream), DiscardNodeAttribution)
|
val parser = Parser(TokenStreamSource(tokenStream), DiscardNodeAttribution)
|
||||||
val unit = parser.parseCompilationUnit()
|
val unit = parser.parseCompilationUnit()
|
||||||
internalUnits[stableKey] = unit
|
internalUnits[stableKey] = unit
|
||||||
@ -33,7 +33,7 @@ class World(val importSource: ImportSource) {
|
|||||||
private fun resolveAllImports(unit: CompilationUnit): Set<CompilationUnit> {
|
private fun resolveAllImports(unit: CompilationUnit): Set<CompilationUnit> {
|
||||||
val units = mutableSetOf<CompilationUnit>()
|
val units = mutableSetOf<CompilationUnit>()
|
||||||
for (declaration in unit.declarations.filterIsInstance<ImportDeclaration>()) {
|
for (declaration in unit.declarations.filterIsInstance<ImportDeclaration>()) {
|
||||||
val importPath = declaration.components.joinToString("/") { it.id } + ".pork"
|
val importPath = declaration.path.components.joinToString("/") { it.id } + ".pork"
|
||||||
val importLocator = ImportLocator(declaration.form.id, importPath)
|
val importLocator = ImportLocator(declaration.form.id, importPath)
|
||||||
val importedUnit = loadOneUnit(importLocator)
|
val importedUnit = loadOneUnit(importLocator)
|
||||||
units.add(importedUnit)
|
units.add(importedUnit)
|
||||||
|
@ -23,7 +23,7 @@ abstract class Tool {
|
|||||||
get() = ImportLocator("local", rootFilePath())
|
get() = ImportLocator("local", rootFilePath())
|
||||||
|
|
||||||
fun tokenize(): TokenStream =
|
fun tokenize(): TokenStream =
|
||||||
Tokenizer(createCharSource()).tokenize()
|
Tokenizer(createCharSource()).stream()
|
||||||
|
|
||||||
fun parse(attribution: NodeAttribution = DiscardNodeAttribution): CompilationUnit =
|
fun parse(attribution: NodeAttribution = DiscardNodeAttribution): CompilationUnit =
|
||||||
Parser(TokenStreamSource(tokenize()), attribution).parseCompilationUnit()
|
Parser(TokenStreamSource(tokenize()), attribution).parseCompilationUnit()
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
package gay.pizza.pork.parser
|
package gay.pizza.pork.parser
|
||||||
|
|
||||||
class BadCharacterError(val char: Char, sourceIndex: SourceIndex) : ParseError(
|
class BadCharacterError(val char: Char, sourceIndex: SourceIndex, state: TokenizerState) : ParseError(
|
||||||
"Failed to produce token for '${char}' at $sourceIndex"
|
"Failed to produce token for '${char}' at $sourceIndex in state $state"
|
||||||
)
|
)
|
||||||
|
@ -2,5 +2,5 @@ package gay.pizza.pork.parser
|
|||||||
|
|
||||||
open class ParseError(val error: String) : RuntimeException() {
|
open class ParseError(val error: String) : RuntimeException() {
|
||||||
override val message: String
|
override val message: String
|
||||||
get() = "${error}\nDescent path: ${ParserStackAnalysis(this).findDescentPath().joinToString(", ")}"
|
get() = "${error}${ParserStackAnalysis(this).buildDescentPathAddendum()}"
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ class Parser(source: TokenSource, attribution: NodeAttribution) :
|
|||||||
val token = peek()
|
val token = peek()
|
||||||
var expression = when (token.type) {
|
var expression = when (token.type) {
|
||||||
TokenType.NumberLiteral -> parseNumberLiteral()
|
TokenType.NumberLiteral -> parseNumberLiteral()
|
||||||
TokenType.StringLiteral -> parseStringLiteral()
|
TokenType.Quote -> parseStringLiteral()
|
||||||
TokenType.True, TokenType.False -> parseBooleanLiteral()
|
TokenType.True, TokenType.False -> parseBooleanLiteral()
|
||||||
TokenType.LeftBracket -> parseListLiteral()
|
TokenType.LeftBracket -> parseListLiteral()
|
||||||
TokenType.Let -> parseLetAssignment()
|
TokenType.Let -> parseLetAssignment()
|
||||||
@ -233,10 +233,14 @@ class Parser(source: TokenSource, attribution: NodeAttribution) :
|
|||||||
|
|
||||||
override fun parseImportDeclaration(): ImportDeclaration = expect(NodeType.ImportDeclaration, TokenType.Import) {
|
override fun parseImportDeclaration(): ImportDeclaration = expect(NodeType.ImportDeclaration, TokenType.Import) {
|
||||||
val form = parseSymbol()
|
val form = parseSymbol()
|
||||||
|
ImportDeclaration(form, parseImportPath())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun parseImportPath(): ImportPath = guarded(NodeType.ImportPath) {
|
||||||
val components = oneAndContinuedBy(TokenType.Dot) {
|
val components = oneAndContinuedBy(TokenType.Dot) {
|
||||||
parseSymbol()
|
parseSymbol()
|
||||||
}
|
}
|
||||||
ImportDeclaration(form, components)
|
ImportPath(components)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun parseIndexedBy(): IndexedBy = guarded(NodeType.IndexedBy) {
|
override fun parseIndexedBy(): IndexedBy = guarded(NodeType.IndexedBy) {
|
||||||
@ -302,7 +306,7 @@ class Parser(source: TokenSource, attribution: NodeAttribution) :
|
|||||||
override fun parseNative(): Native = expect(NodeType.Native, TokenType.Native) {
|
override fun parseNative(): Native = expect(NodeType.Native, TokenType.Native) {
|
||||||
val form = parseSymbol()
|
val form = parseSymbol()
|
||||||
val definitions = mutableListOf<StringLiteral>()
|
val definitions = mutableListOf<StringLiteral>()
|
||||||
while (peek(TokenType.StringLiteral)) {
|
while (peek(TokenType.Quote)) {
|
||||||
definitions.add(parseStringLiteral())
|
definitions.add(parseStringLiteral())
|
||||||
}
|
}
|
||||||
Native(form, definitions)
|
Native(form, definitions)
|
||||||
@ -333,8 +337,11 @@ class Parser(source: TokenSource, attribution: NodeAttribution) :
|
|||||||
SetAssignment(symbol, value)
|
SetAssignment(symbol, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun parseStringLiteral(): StringLiteral = expect(NodeType.StringLiteral, TokenType.StringLiteral) {
|
override fun parseStringLiteral(): StringLiteral = guarded(NodeType.StringLiteral) {
|
||||||
val content = StringEscape.unescape(StringEscape.unquote(it.text))
|
expect(TokenType.Quote)
|
||||||
|
val stringLiteralToken = expect(TokenType.StringLiteral)
|
||||||
|
expect(TokenType.Quote)
|
||||||
|
val content = StringEscape.unescape(stringLiteralToken.text)
|
||||||
StringLiteral(content)
|
StringLiteral(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,4 +24,13 @@ class ParserStackAnalysis(private val stack: Array<StackTraceElement>) {
|
|||||||
}
|
}
|
||||||
return parseDescentPaths.reversed()
|
return parseDescentPaths.reversed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun buildDescentPathAddendum(): String {
|
||||||
|
val descentPath = findDescentPath()
|
||||||
|
if (descentPath.isEmpty()) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return "\nParser descent path: ${descentPath.joinToString(", ")}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -238,6 +238,10 @@ class Printer(buffer: StringBuilder) : NodeVisitor<Unit> {
|
|||||||
append("import ")
|
append("import ")
|
||||||
visit(node.form)
|
visit(node.form)
|
||||||
append(" ")
|
append(" ")
|
||||||
|
visit(node.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitImportPath(node: ImportPath) {
|
||||||
for ((index, component) in node.components.withIndex()) {
|
for ((index, component) in node.components.withIndex()) {
|
||||||
visit(component)
|
visit(component)
|
||||||
if (index != node.components.size - 1) {
|
if (index != node.components.size - 1) {
|
||||||
|
@ -2,12 +2,7 @@ package gay.pizza.pork.parser
|
|||||||
|
|
||||||
object StringCharConsumer : CharConsumer {
|
object StringCharConsumer : CharConsumer {
|
||||||
override fun consume(type: TokenType, tokenizer: Tokenizer): String? {
|
override fun consume(type: TokenType, tokenizer: Tokenizer): String? {
|
||||||
if (!tokenizer.peek("\"")) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
val buffer = StringBuilder()
|
val buffer = StringBuilder()
|
||||||
buffer.append(tokenizer.source.next())
|
|
||||||
var escape = false
|
var escape = false
|
||||||
while (true) {
|
while (true) {
|
||||||
val char = tokenizer.source.peek()
|
val char = tokenizer.source.peek()
|
||||||
@ -16,12 +11,14 @@ object StringCharConsumer : CharConsumer {
|
|||||||
throw UnterminatedTokenError("String", tokenizer.source.currentSourceIndex())
|
throw UnterminatedTokenError("String", tokenizer.source.currentSourceIndex())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (char == '"' && !escape) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
buffer.append(tokenizer.source.next())
|
buffer.append(tokenizer.source.next())
|
||||||
|
|
||||||
if (char == '\\') {
|
if (char == '\\') {
|
||||||
escape = true
|
escape = true
|
||||||
} else if (char == '"' && !escape) {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return buffer.toString()
|
return buffer.toString()
|
||||||
|
@ -3,5 +3,4 @@ package gay.pizza.pork.parser
|
|||||||
object StringEscape {
|
object StringEscape {
|
||||||
fun escape(input: String): String = input.replace("\n", "\\n")
|
fun escape(input: String): String = input.replace("\n", "\\n")
|
||||||
fun unescape(input: String): String = input.replace("\\n", "\n")
|
fun unescape(input: String): String = input.replace("\\n", "\n")
|
||||||
fun unquote(input: String): String = input.substring(1, input.length - 1)
|
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import gay.pizza.pork.parser.TokenTypeProperty.*
|
|||||||
import gay.pizza.pork.parser.TokenFamily.*
|
import gay.pizza.pork.parser.TokenFamily.*
|
||||||
import gay.pizza.pork.parser.TokenTypeProperty.AnyOf
|
import gay.pizza.pork.parser.TokenTypeProperty.AnyOf
|
||||||
|
|
||||||
enum class TokenType(vararg properties: TokenTypeProperty) {
|
enum class TokenType(vararg val properties: TokenTypeProperty) {
|
||||||
NumberLiteral(NumericLiteralFamily, CharMatch(CharMatcher.AnyOf(
|
NumberLiteral(NumericLiteralFamily, CharMatch(CharMatcher.AnyOf(
|
||||||
MatchRange('0'..'9'),
|
MatchRange('0'..'9'),
|
||||||
NotAtIndex(0, MatchSingle('.'))
|
NotAtIndex(0, MatchSingle('.'))
|
||||||
@ -17,7 +17,8 @@ enum class TokenType(vararg properties: TokenTypeProperty) {
|
|||||||
MatchRange('0' .. '9'),
|
MatchRange('0' .. '9'),
|
||||||
MatchSingle('_')
|
MatchSingle('_')
|
||||||
)), KeywordUpgrader),
|
)), KeywordUpgrader),
|
||||||
StringLiteral(StringLiteralFamily, CharConsume(StringCharConsumer)),
|
Quote(StringLiteralFamily, SingleChar('"'), InsideStates(TokenizerState.Normal, TokenizerState.StringLiteralEnd)),
|
||||||
|
StringLiteral(StringLiteralFamily, CharConsume(StringCharConsumer), InsideStates(TokenizerState.StringLiteralStart)),
|
||||||
Equality(OperatorFamily),
|
Equality(OperatorFamily),
|
||||||
Inequality(ManyChars("!="), OperatorFamily),
|
Inequality(ManyChars("!="), OperatorFamily),
|
||||||
ExclamationPoint(SingleChar('!'), Promotion('=', Inequality)),
|
ExclamationPoint(SingleChar('!'), Promotion('=', Inequality)),
|
||||||
@ -91,6 +92,11 @@ enum class TokenType(vararg properties: TokenTypeProperty) {
|
|||||||
val charConsume: CharConsume? = properties.filterIsInstance<CharConsume>().singleOrNull()
|
val charConsume: CharConsume? = properties.filterIsInstance<CharConsume>().singleOrNull()
|
||||||
val tokenUpgrader: TokenUpgrader? =
|
val tokenUpgrader: TokenUpgrader? =
|
||||||
properties.filterIsInstance<TokenUpgrader>().singleOrNull()
|
properties.filterIsInstance<TokenUpgrader>().singleOrNull()
|
||||||
|
val validStates: List<TokenizerState> by lazy {
|
||||||
|
properties
|
||||||
|
.filterIsInstance<InsideStates>()
|
||||||
|
.singleOrNull()?.states?.toList() ?: listOf(TokenizerState.Normal)
|
||||||
|
}
|
||||||
|
|
||||||
val simpleWantString: String? = manyChars?.text ?: singleChar?.char?.toString()
|
val simpleWantString: String? = manyChars?.text ?: singleChar?.char?.toString()
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ interface TokenTypeProperty {
|
|||||||
class Promotion(val nextChar: Char, val type: TokenType) : TokenTypeProperty
|
class Promotion(val nextChar: Char, val type: TokenType) : TokenTypeProperty
|
||||||
class ManyChars(val text: String) : TokenTypeProperty
|
class ManyChars(val text: String) : TokenTypeProperty
|
||||||
class AnyOf(vararg val strings: String): TokenTypeProperty
|
class AnyOf(vararg val strings: String): TokenTypeProperty
|
||||||
|
class InsideStates(vararg val states: TokenizerState) : TokenTypeProperty
|
||||||
open class CharMatch(val matcher: CharMatcher) : TokenTypeProperty
|
open class CharMatch(val matcher: CharMatcher) : TokenTypeProperty
|
||||||
open class CharConsume(val consumer: CharConsumer) : TokenTypeProperty
|
open class CharConsume(val consumer: CharConsumer) : TokenTypeProperty
|
||||||
open class TokenUpgrader(val maybeUpgrade: (Token) -> Token?) : TokenTypeProperty
|
open class TokenUpgrader(val maybeUpgrade: (Token) -> Token?) : TokenTypeProperty
|
||||||
|
@ -2,73 +2,100 @@ package gay.pizza.pork.parser
|
|||||||
|
|
||||||
class Tokenizer(source: CharSource) {
|
class Tokenizer(source: CharSource) {
|
||||||
val source: SourceIndexCharSource = SourceIndexCharSource(source)
|
val source: SourceIndexCharSource = SourceIndexCharSource(source)
|
||||||
|
|
||||||
private var startIndex: SourceIndex = SourceIndex.zero()
|
private var startIndex: SourceIndex = SourceIndex.zero()
|
||||||
|
private var state = TokenizerState.Normal
|
||||||
|
|
||||||
fun next(): Token {
|
private fun nextTokenOrNull(): Token? {
|
||||||
while (source.peek() != CharSource.EndOfFile) {
|
if (source.peek() == CharSource.EndOfFile) {
|
||||||
startIndex = source.currentSourceIndex()
|
source.next()
|
||||||
|
return Token.endOfFile(source.currentSourceIndex())
|
||||||
for (item in TokenType.CharConsumes) {
|
|
||||||
val text = item.charConsume!!.consumer.consume(item, this)
|
|
||||||
if (text != null) {
|
|
||||||
return produceToken(item, text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val char = source.next()
|
|
||||||
|
|
||||||
for (item in TokenType.SingleChars) {
|
|
||||||
val itemChar = item.singleChar!!.char
|
|
||||||
if (itemChar != char) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var type = item
|
|
||||||
var text = itemChar.toString()
|
|
||||||
var promoted = true
|
|
||||||
while (promoted) {
|
|
||||||
promoted = false
|
|
||||||
for (promotion in type.promotions) {
|
|
||||||
if (source.peek() != promotion.nextChar) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
val nextChar = source.next()
|
|
||||||
type = promotion.type
|
|
||||||
text += nextChar
|
|
||||||
promoted = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return produceToken(type, text)
|
|
||||||
}
|
|
||||||
|
|
||||||
var index = 0
|
|
||||||
for (item in TokenType.CharMatches) {
|
|
||||||
if (!item.charMatch!!.matcher.valid(char, index)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
val text = buildString {
|
|
||||||
append(char)
|
|
||||||
|
|
||||||
while (item.charMatch.matcher.valid(source.peek(), ++index)) {
|
|
||||||
append(source.next())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var token = produceToken(item, text)
|
|
||||||
val tokenUpgrader = item.tokenUpgrader
|
|
||||||
if (tokenUpgrader != null) {
|
|
||||||
token = tokenUpgrader.maybeUpgrade(token) ?: token
|
|
||||||
}
|
|
||||||
return token
|
|
||||||
}
|
|
||||||
|
|
||||||
throw BadCharacterError(char, startIndex)
|
|
||||||
}
|
}
|
||||||
return Token.endOfFile(startIndex.copy(index = source.currentIndex))
|
|
||||||
|
startIndex = source.currentSourceIndex()
|
||||||
|
|
||||||
|
for (item in TokenType.CharConsumes) {
|
||||||
|
if (!item.validStates.contains(state)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val text = item.charConsume!!.consumer.consume(item, this)
|
||||||
|
if (text != null) {
|
||||||
|
return produceToken(item, text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val char = source.next()
|
||||||
|
|
||||||
|
for (item in TokenType.SingleChars) {
|
||||||
|
if (!item.validStates.contains(state)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val itemChar = item.singleChar!!.char
|
||||||
|
if (itemChar != char) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var type = item
|
||||||
|
var text = itemChar.toString()
|
||||||
|
var promoted = true
|
||||||
|
while (promoted) {
|
||||||
|
promoted = false
|
||||||
|
for (promotion in type.promotions) {
|
||||||
|
if (source.peek() != promotion.nextChar) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val nextChar = source.next()
|
||||||
|
type = promotion.type
|
||||||
|
text += nextChar
|
||||||
|
promoted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return produceToken(type, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = 0
|
||||||
|
for (item in TokenType.CharMatches) {
|
||||||
|
if (!item.validStates.contains(state)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!item.charMatch!!.matcher.valid(char, index)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val text = buildString {
|
||||||
|
append(char)
|
||||||
|
|
||||||
|
while (item.charMatch.matcher.valid(source.peek(), ++index)) {
|
||||||
|
append(source.next())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var token = produceToken(item, text)
|
||||||
|
val tokenUpgrader = item.tokenUpgrader
|
||||||
|
if (tokenUpgrader != null) {
|
||||||
|
token = tokenUpgrader.maybeUpgrade(token) ?: token
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun tokenize(): TokenStream {
|
fun next(): Token {
|
||||||
|
val what = source.peek()
|
||||||
|
val token = nextTokenOrNull()
|
||||||
|
if (token != null) {
|
||||||
|
for (transition in state.transitions) {
|
||||||
|
if (transition.produced == token.type) {
|
||||||
|
state = transition.enter
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
throw BadCharacterError(what, source.currentSourceIndex(), state)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stream(): TokenStream {
|
||||||
val tokens = mutableListOf<Token>()
|
val tokens = mutableListOf<Token>()
|
||||||
while (true) {
|
while (true) {
|
||||||
val token = next()
|
val token = next()
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
package gay.pizza.pork.parser
|
||||||
|
|
||||||
|
enum class TokenizerState(vararg val transitions: Transition) {
|
||||||
|
Normal(Transition({ TokenType.Quote }) { StringLiteralStart }),
|
||||||
|
StringLiteralStart(Transition({ TokenType.StringLiteral }) { StringLiteralEnd }),
|
||||||
|
StringLiteralEnd(Transition({ TokenType.Quote }) { Normal });
|
||||||
|
|
||||||
|
data class Transition(private val producedToken: () -> TokenType, private val nextState: () -> TokenizerState) {
|
||||||
|
val produced by lazy { producedToken() }
|
||||||
|
val enter by lazy { nextState() }
|
||||||
|
}
|
||||||
|
}
|
11
stdlib/src/main/pork/ffi/struct.pork
Normal file
11
stdlib/src/main/pork/ffi/struct.pork
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export func ffiStructDefine(items...)
|
||||||
|
native ffi "internal" "ffiStructDefine"
|
||||||
|
|
||||||
|
export func ffiStructAllocate(struct)
|
||||||
|
native ffi "internal" "ffiStructAllocate"
|
||||||
|
|
||||||
|
export func ffiStructValue(struct, field, value)
|
||||||
|
native ffi "internal" "ffiStructValue"
|
||||||
|
|
||||||
|
export func ffiStructBytes(struct, value)
|
||||||
|
native ffi "internal" "ffiStructBytes"
|
@ -8,13 +8,14 @@ import com.intellij.platform.backend.navigation.NavigationRequest
|
|||||||
import com.intellij.platform.backend.navigation.NavigationTarget
|
import com.intellij.platform.backend.navigation.NavigationTarget
|
||||||
import com.intellij.platform.backend.presentation.TargetPresentation
|
import com.intellij.platform.backend.presentation.TargetPresentation
|
||||||
import gay.pizza.pork.idea.psi.gen.PorkElement
|
import gay.pizza.pork.idea.psi.gen.PorkElement
|
||||||
|
import gay.pizza.pork.idea.resolution.PorkReferenceResolution
|
||||||
|
|
||||||
@Suppress("UnstableApiUsage")
|
@Suppress("UnstableApiUsage")
|
||||||
data class PorkDeclarationSymbol(val module: String, val name: String) : Symbol, NavigatableSymbol {
|
data class PorkDeclarationSymbol(val module: String, val name: String) : Symbol, NavigatableSymbol {
|
||||||
override fun createPointer(): Pointer<out Symbol> = Pointer { this }
|
override fun createPointer(): Pointer<out Symbol> = Pointer { this }
|
||||||
override fun getNavigationTargets(project: Project): MutableCollection<out NavigationTarget> {
|
override fun getNavigationTargets(project: Project): MutableCollection<out NavigationTarget> {
|
||||||
return PorkReferenceResolution.getAllProjectPorkFiles(project)
|
return PorkReferenceResolution.getAllProjectPorkFiles(project)
|
||||||
.flatMap { PorkReferenceResolution.findAnyDefinitions(it) }
|
.flatMap { PorkReferenceResolution.findAnyDefinitions(it.file) }
|
||||||
.map { PorkNavigationTarget(it) }
|
.map { PorkNavigationTarget(it) }
|
||||||
.toMutableList()
|
.toMutableList()
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,10 @@ object PorkElementTypes {
|
|||||||
elementTypeFor(TokenType.StringLiteral)
|
elementTypeFor(TokenType.StringLiteral)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val QuoteSet = TokenSet.create(
|
||||||
|
elementTypeFor(TokenType.Quote)
|
||||||
|
)
|
||||||
|
|
||||||
fun tokenTypeFor(elementType: IElementType): TokenType? =
|
fun tokenTypeFor(elementType: IElementType): TokenType? =
|
||||||
elementTypeToTokenType[elementType]
|
elementTypeToTokenType[elementType]
|
||||||
|
|
||||||
|
@ -10,4 +10,8 @@ class PorkFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, PorkL
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String = "Pork"
|
override fun toString(): String = "Pork"
|
||||||
|
|
||||||
|
override fun isPhysical(): Boolean {
|
||||||
|
return super.isPhysical()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
package gay.pizza.pork.idea
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.hints.InlayInfo
|
||||||
|
import com.intellij.codeInsight.hints.InlayParameterHintsProvider
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.psi.util.childrenOfType
|
||||||
|
import gay.pizza.pork.idea.psi.gen.ArgumentSpecElement
|
||||||
|
import gay.pizza.pork.idea.psi.gen.FunctionDefinitionElement
|
||||||
|
|
||||||
|
@Suppress("UnstableApiUsage")
|
||||||
|
class PorkInlayParameterHintsProvider : InlayParameterHintsProvider {
|
||||||
|
override fun getParameterHints(element: PsiElement): MutableList<InlayInfo> {
|
||||||
|
val inlays = mutableListOf<InlayInfo>()
|
||||||
|
val resolved = element.reference?.resolve()
|
||||||
|
if (resolved !is FunctionDefinitionElement) {
|
||||||
|
return inlays
|
||||||
|
}
|
||||||
|
val argumentSpecs = resolved.childrenOfType<ArgumentSpecElement>()
|
||||||
|
val arguments = if (element.children.isNotEmpty()) {
|
||||||
|
element.children.drop(1)
|
||||||
|
} else emptyList()
|
||||||
|
|
||||||
|
for ((argument, spec) in arguments.zip(argumentSpecs)) {
|
||||||
|
val name = spec.name
|
||||||
|
if (name != null) {
|
||||||
|
inlays.add(InlayInfo(name, argument.textOffset))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return inlays
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDefaultBlackList(): MutableSet<String> =
|
||||||
|
mutableSetOf()
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
package gay.pizza.pork.idea
|
||||||
|
|
||||||
|
import com.intellij.lang.parameterInfo.CreateParameterInfoContext
|
||||||
|
import com.intellij.lang.parameterInfo.ParameterInfoHandler
|
||||||
|
import com.intellij.lang.parameterInfo.ParameterInfoUIContext
|
||||||
|
import com.intellij.lang.parameterInfo.ParameterInfoUtils
|
||||||
|
import com.intellij.lang.parameterInfo.UpdateParameterInfoContext
|
||||||
|
import com.intellij.openapi.util.TextRange
|
||||||
|
import com.intellij.psi.util.childrenOfType
|
||||||
|
import com.intellij.psi.util.elementsAtOffsetUp
|
||||||
|
import gay.pizza.pork.idea.psi.gen.ArgumentSpecElement
|
||||||
|
import gay.pizza.pork.idea.psi.gen.FunctionCallElement
|
||||||
|
import gay.pizza.pork.idea.psi.gen.FunctionDefinitionElement
|
||||||
|
import gay.pizza.pork.parser.TokenType
|
||||||
|
|
||||||
|
@Suppress("UnstableApiUsage")
|
||||||
|
class PorkParameterInfoHandler : ParameterInfoHandler<FunctionCallElement, FunctionDefinitionElement> {
|
||||||
|
override fun findElementForParameterInfo(context: CreateParameterInfoContext): FunctionCallElement? {
|
||||||
|
return context.file.elementsAtOffsetUp(context.offset).asSequence()
|
||||||
|
.map { it.first }
|
||||||
|
.filterIsInstance<FunctionCallElement>()
|
||||||
|
.firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findElementForUpdatingParameterInfo(context: UpdateParameterInfoContext): FunctionCallElement? {
|
||||||
|
return context.file.elementsAtOffsetUp(context.offset).asSequence()
|
||||||
|
.map { it.first }
|
||||||
|
.filterIsInstance<FunctionCallElement>()
|
||||||
|
.firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateUI(p: FunctionDefinitionElement, context: ParameterInfoUIContext) {
|
||||||
|
val argumentSpecs = p.childrenOfType<ArgumentSpecElement>()
|
||||||
|
val signature = argumentSpecs.mapNotNull { it.name }.joinToString(", ")
|
||||||
|
if (argumentSpecs.isEmpty()) {
|
||||||
|
context.setupUIComponentPresentation(
|
||||||
|
"<no parameters>",
|
||||||
|
-1,
|
||||||
|
-1,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
context.defaultParameterColor
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (context.currentParameterIndex >= argumentSpecs.size) {
|
||||||
|
context.setupUIComponentPresentation(
|
||||||
|
signature,
|
||||||
|
-1,
|
||||||
|
-1,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
context.defaultParameterColor
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
var range: TextRange? = null
|
||||||
|
var start = 0
|
||||||
|
for ((index, item) in signature.split(", ").withIndex()) {
|
||||||
|
if (index == context.currentParameterIndex) {
|
||||||
|
range = TextRange(index, index + item.length)
|
||||||
|
}
|
||||||
|
start += item.length + 2
|
||||||
|
}
|
||||||
|
context.setupUIComponentPresentation(
|
||||||
|
signature,
|
||||||
|
range?.startOffset ?: 0,
|
||||||
|
range?.endOffset ?: (signature.length - 1),
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
context.defaultParameterColor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateParameterInfo(parameterOwner: FunctionCallElement, context: UpdateParameterInfoContext) {
|
||||||
|
val offset = ParameterInfoUtils.getCurrentParameterIndex(
|
||||||
|
parameterOwner.node,
|
||||||
|
context.offset,
|
||||||
|
PorkElementTypes.elementTypeFor(TokenType.Comma)
|
||||||
|
)
|
||||||
|
context.setCurrentParameter(offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showParameterInfo(element: FunctionCallElement, context: CreateParameterInfoContext) {
|
||||||
|
context.showHint(element, element.textOffset, this)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package gay.pizza.pork.idea
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.editorActions.SimpleTokenSetQuoteHandler
|
||||||
|
|
||||||
|
class PorkQuoteHandler : SimpleTokenSetQuoteHandler(PorkElementTypes.QuoteSet)
|
@ -1,109 +0,0 @@
|
|||||||
package gay.pizza.pork.idea
|
|
||||||
|
|
||||||
import com.intellij.openapi.project.Project
|
|
||||||
import com.intellij.psi.PsiElement
|
|
||||||
import com.intellij.psi.PsiFile
|
|
||||||
import com.intellij.psi.PsiManager
|
|
||||||
import com.intellij.psi.search.FilenameIndex
|
|
||||||
import com.intellij.psi.util.PsiTreeUtil
|
|
||||||
import com.intellij.psi.util.childrenOfType
|
|
||||||
import gay.pizza.pork.idea.psi.gen.*
|
|
||||||
|
|
||||||
object PorkReferenceResolution {
|
|
||||||
fun getRelevantFiles(containingFile: PsiFile): List<PsiFile> {
|
|
||||||
if (containingFile.virtualFile == null) {
|
|
||||||
return getAllProjectPorkFiles(containingFile.project)
|
|
||||||
}
|
|
||||||
val importDeclarationElements = PsiTreeUtil.collectElementsOfType(containingFile, ImportDeclarationElement::class.java)
|
|
||||||
val files = mutableListOf(containingFile)
|
|
||||||
for (importDeclaration in importDeclarationElements) {
|
|
||||||
val symbolElements = importDeclaration.childrenOfType<SymbolElement>()
|
|
||||||
val importType = importDeclaration.childrenOfType<SymbolElement>().first().text
|
|
||||||
if (importType != "local") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
val basicImportPath = symbolElements.drop(1).joinToString("/") { it.text.trim() }
|
|
||||||
val actualImportPath = "../${basicImportPath}.pork"
|
|
||||||
val virtualFile = containingFile.virtualFile?.findFileByRelativePath(actualImportPath) ?: continue
|
|
||||||
val psiFile = PsiManager.getInstance(containingFile.project).findFile(virtualFile) ?: continue
|
|
||||||
files.add(psiFile)
|
|
||||||
}
|
|
||||||
return files
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getAllProjectPorkFiles(project: Project): List<PsiFile> {
|
|
||||||
val porkVirtualFiles = FilenameIndex.getAllFilesByExt(project, "pork")
|
|
||||||
return porkVirtualFiles.mapNotNull { virtualFile ->
|
|
||||||
PsiManager.getInstance(project).findFile(virtualFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun findAllCandidates(internalPorkElement: PorkElement, name: String? = null): List<PorkElement> =
|
|
||||||
listOf(findAnyLocals(internalPorkElement, name), findAnyDefinitions(internalPorkElement.containingFile, name)).flatten()
|
|
||||||
|
|
||||||
fun findAnyLocals(internalPorkElement: PorkElement, name: String? = null): List<PorkElement> {
|
|
||||||
val functionDefinitionElement = PsiTreeUtil.getParentOfType(internalPorkElement, FunctionDefinitionElement::class.java)
|
|
||||||
?: return emptyList()
|
|
||||||
val locals = mutableListOf<PorkElement>()
|
|
||||||
|
|
||||||
fun check(localCandidate: PsiElement, upward: Boolean) {
|
|
||||||
if (localCandidate is BlockElement && !upward) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (localCandidate is ArgumentSpecElement ||
|
|
||||||
localCandidate is LetAssignmentElement ||
|
|
||||||
localCandidate is VarAssignmentElement) {
|
|
||||||
locals.add(localCandidate as PorkElement)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (localCandidate is ForInElement) {
|
|
||||||
val forInItem = localCandidate.childrenOfType<ForInItemElement>().firstOrNull()
|
|
||||||
if (forInItem != null) {
|
|
||||||
locals.add(forInItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
localCandidate.children.forEach { check(it, false) }
|
|
||||||
}
|
|
||||||
|
|
||||||
PsiTreeUtil.treeWalkUp(internalPorkElement, functionDefinitionElement) { _, localCandidate ->
|
|
||||||
if (localCandidate != null) {
|
|
||||||
if (internalPorkElement == functionDefinitionElement) {
|
|
||||||
return@treeWalkUp true
|
|
||||||
}
|
|
||||||
check(localCandidate, true)
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
val argumentSpecElements = functionDefinitionElement.childrenOfType<ArgumentSpecElement>()
|
|
||||||
locals.addAll(argumentSpecElements)
|
|
||||||
val finalLocals = locals.distinctBy { it.textRange }
|
|
||||||
return finalLocals.filter { if (name != null) it.name == name else true }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun findAnyDefinitions(containingFile: PsiFile, name: String? = null): List<PorkElement> {
|
|
||||||
val foundDefinitions = mutableListOf<PorkNamedElement>()
|
|
||||||
for (file in getRelevantFiles(containingFile)) {
|
|
||||||
val definitions = PsiTreeUtil.collectElements(file) { element ->
|
|
||||||
element is FunctionDefinitionElement ||
|
|
||||||
element is LetDefinitionElement
|
|
||||||
}.filterIsInstance<PorkNamedElement>()
|
|
||||||
if (name != null) {
|
|
||||||
val fileFoundDefinition = definitions.firstOrNull {
|
|
||||||
it.name == name
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileFoundDefinition != null) {
|
|
||||||
foundDefinitions.add(fileFoundDefinition)
|
|
||||||
return foundDefinitions
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
foundDefinitions.addAll(definitions)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return foundDefinitions
|
|
||||||
}
|
|
||||||
}
|
|
@ -49,7 +49,13 @@ object PorkElementHelpers {
|
|||||||
|
|
||||||
fun referenceOfElement(element: PorkElement, type: NodeType): PsiReference? {
|
fun referenceOfElement(element: PorkElement, type: NodeType): PsiReference? {
|
||||||
unused(type)
|
unused(type)
|
||||||
val textRangeOfSymbolInElement = element.childrenOfType<SymbolElement>().firstOrNull()?.textRangeInParent ?: return null
|
|
||||||
|
if (element is ImportPathElement) {
|
||||||
|
return PorkFileReference(element, element.textRange)
|
||||||
|
}
|
||||||
|
|
||||||
|
val symbols = element.childrenOfType<SymbolElement>()
|
||||||
|
val textRangeOfSymbolInElement = symbols.firstOrNull()?.textRangeInParent ?: return null
|
||||||
return PorkIdentifierReference(element, textRangeOfSymbolInElement)
|
return PorkIdentifierReference(element, textRangeOfSymbolInElement)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
package gay.pizza.pork.idea.psi
|
||||||
|
|
||||||
|
import com.intellij.openapi.util.TextRange
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.psi.util.parentOfType
|
||||||
|
import gay.pizza.pork.idea.psi.gen.ImportDeclarationElement
|
||||||
|
import gay.pizza.pork.idea.psi.gen.PorkElement
|
||||||
|
import gay.pizza.pork.idea.resolution.PorkReferenceResolution
|
||||||
|
|
||||||
|
class PorkFileReference(element: PorkElement, textRange: TextRange) : PorkReference(element, textRange) {
|
||||||
|
override fun resolve(): PsiElement? {
|
||||||
|
val importDeclarationElement = element.parentOfType<ImportDeclarationElement>() ?: return null
|
||||||
|
val resolved = PorkReferenceResolution.resolveImportFile(
|
||||||
|
element.containingFile,
|
||||||
|
PorkReferenceResolution.findPorkStdDirectory(element.project),
|
||||||
|
importDeclarationElement
|
||||||
|
)
|
||||||
|
return resolved?.file
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getVariants(): Array<Any> = arrayOf()
|
||||||
|
}
|
@ -1,15 +1,17 @@
|
|||||||
package gay.pizza.pork.idea.psi
|
package gay.pizza.pork.idea.psi
|
||||||
|
|
||||||
import com.intellij.psi.PsiFile
|
import gay.pizza.pork.idea.resolution.PorkReferenceResolution
|
||||||
import gay.pizza.pork.idea.PorkReferenceResolution
|
|
||||||
import gay.pizza.pork.idea.psi.gen.PorkElement
|
import gay.pizza.pork.idea.psi.gen.PorkElement
|
||||||
|
import gay.pizza.pork.idea.resolution.PorkReferenceRelevantFile
|
||||||
|
|
||||||
interface PorkReferencable {
|
interface PorkReferencable {
|
||||||
val internalPorkElement: PorkElement
|
val internalPorkElement: PorkElement
|
||||||
|
|
||||||
fun getRelevantFiles(): List<PsiFile> = PorkReferenceResolution.getRelevantFiles(internalPorkElement.containingFile)
|
fun getRelevantFiles(): List<PorkReferenceRelevantFile> =
|
||||||
|
PorkReferenceResolution.getRelevantFiles(internalPorkElement.containingFile)
|
||||||
|
|
||||||
fun findAllCandidates(name: String? = null): List<PorkElement> =
|
fun findAllCandidates(name: String? = null): List<PorkElement> =
|
||||||
listOf(findAnyLocals(name), findAnyDefinitions(name)).flatten()
|
PorkReferenceResolution.findAllCandidates(internalPorkElement, name)
|
||||||
|
|
||||||
fun findAnyLocals(name: String? = null): List<PorkElement> =
|
fun findAnyLocals(name: String? = null): List<PorkElement> =
|
||||||
PorkReferenceResolution.findAnyLocals(internalPorkElement, name)
|
PorkReferenceResolution.findAnyLocals(internalPorkElement, name)
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
// GENERATED CODE FROM PORK AST CODEGEN
|
||||||
|
package gay.pizza.pork.idea.psi.gen
|
||||||
|
|
||||||
|
import com.intellij.lang.ASTNode
|
||||||
|
import com.intellij.navigation.ItemPresentation
|
||||||
|
import com.intellij.psi.PsiReference
|
||||||
|
import gay.pizza.pork.ast.gen.NodeType
|
||||||
|
import gay.pizza.pork.idea.psi.PorkElementHelpers
|
||||||
|
import javax.swing.Icon
|
||||||
|
|
||||||
|
class ImportPathElement(node: ASTNode) : PorkElement(node) {
|
||||||
|
override fun getReference(): PsiReference? =
|
||||||
|
PorkElementHelpers.referenceOfElement(this, NodeType.CompilationUnit)
|
||||||
|
|
||||||
|
override fun getIcon(flags: Int): Icon? =
|
||||||
|
PorkElementHelpers.iconOf(this)
|
||||||
|
|
||||||
|
override fun getPresentation(): ItemPresentation? =
|
||||||
|
PorkElementHelpers.presentationOf(this)
|
||||||
|
}
|
@ -23,6 +23,7 @@ object PorkElementFactory {
|
|||||||
NodeType.FunctionDefinition -> FunctionDefinitionElement(node)
|
NodeType.FunctionDefinition -> FunctionDefinitionElement(node)
|
||||||
NodeType.LetDefinition -> LetDefinitionElement(node)
|
NodeType.LetDefinition -> LetDefinitionElement(node)
|
||||||
NodeType.If -> IfElement(node)
|
NodeType.If -> IfElement(node)
|
||||||
|
NodeType.ImportPath -> ImportPathElement(node)
|
||||||
NodeType.ImportDeclaration -> ImportDeclarationElement(node)
|
NodeType.ImportDeclaration -> ImportDeclarationElement(node)
|
||||||
NodeType.IntegerLiteral -> IntegerLiteralElement(node)
|
NodeType.IntegerLiteral -> IntegerLiteralElement(node)
|
||||||
NodeType.LongLiteral -> LongLiteralElement(node)
|
NodeType.LongLiteral -> LongLiteralElement(node)
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
package gay.pizza.pork.idea.resolution
|
||||||
|
|
||||||
|
import com.intellij.psi.PsiFile
|
||||||
|
|
||||||
|
class PorkReferenceRelevantFile(val file: PsiFile, val type: PorkRelevantFileType)
|
@ -0,0 +1,197 @@
|
|||||||
|
package gay.pizza.pork.idea.resolution
|
||||||
|
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.project.guessProjectDir
|
||||||
|
import com.intellij.openapi.vfs.*
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.psi.PsiFile
|
||||||
|
import com.intellij.psi.PsiManager
|
||||||
|
import com.intellij.psi.search.FilenameIndex
|
||||||
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
|
import com.intellij.psi.util.childrenOfType
|
||||||
|
import gay.pizza.pork.idea.psi.gen.*
|
||||||
|
|
||||||
|
object PorkReferenceResolution {
|
||||||
|
fun getRelevantFiles(containingFile: PsiFile): List<PorkReferenceRelevantFile> {
|
||||||
|
if (containingFile.virtualFile == null) {
|
||||||
|
return listOf(
|
||||||
|
getAllProjectPorkFiles(containingFile.project),
|
||||||
|
getAllPorkStdFiles(containingFile.project)
|
||||||
|
).flatten()
|
||||||
|
}
|
||||||
|
val importDeclarationElements = PsiTreeUtil.collectElementsOfType(
|
||||||
|
containingFile,
|
||||||
|
ImportDeclarationElement::class.java
|
||||||
|
)
|
||||||
|
val files = mutableListOf(PorkReferenceRelevantFile(containingFile, PorkRelevantFileType.Self))
|
||||||
|
val stdDirectory = findPorkStdDirectory(containingFile.project)
|
||||||
|
val prelude = resolveStdImport(containingFile, stdDirectory, "lang/prelude")
|
||||||
|
if (prelude != null) {
|
||||||
|
files.add(prelude)
|
||||||
|
}
|
||||||
|
for (importDeclaration in importDeclarationElements) {
|
||||||
|
val resolved = resolveImportFile(containingFile, stdDirectory, importDeclaration)
|
||||||
|
if (resolved != null) {
|
||||||
|
files.add(resolved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resolveImportFile(
|
||||||
|
containingFile: PsiFile,
|
||||||
|
stdDirectory: VirtualFile?,
|
||||||
|
importDeclarationElement: ImportDeclarationElement
|
||||||
|
): PorkReferenceRelevantFile? {
|
||||||
|
val importType = importDeclarationElement.childrenOfType<SymbolElement>().firstOrNull()?.text ?: return null
|
||||||
|
val importPathElement = importDeclarationElement.childrenOfType<ImportPathElement>().firstOrNull() ?: return null
|
||||||
|
val basicImportPath = importPathElement.children.joinToString("/") { it.text.trim() }
|
||||||
|
return when (importType) {
|
||||||
|
"local" -> {
|
||||||
|
val actualImportPath = "../${basicImportPath}.pork"
|
||||||
|
val actualVirtualFile = containingFile.virtualFile?.findFileByRelativePath(actualImportPath) ?: return null
|
||||||
|
referenceRelevantFile(containingFile.project, actualVirtualFile, PorkRelevantFileType.Local)
|
||||||
|
}
|
||||||
|
"std" -> {
|
||||||
|
resolveStdImport(containingFile, stdDirectory, basicImportPath)
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolveStdImport(containingFile: PsiFile, stdDirectory: VirtualFile?, basicImportPath: String): PorkReferenceRelevantFile? {
|
||||||
|
if (stdDirectory == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val actualVirtualFile = stdDirectory.findFile("${basicImportPath}.pork") ?: return null
|
||||||
|
return referenceRelevantFile(containingFile.project, actualVirtualFile, PorkRelevantFileType.Std)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun referenceRelevantFile(
|
||||||
|
project: Project,
|
||||||
|
virtualFile: VirtualFile,
|
||||||
|
type: PorkRelevantFileType
|
||||||
|
): PorkReferenceRelevantFile? {
|
||||||
|
val psiFile = PsiManager.getInstance(project).findFile(virtualFile) ?: return null
|
||||||
|
return PorkReferenceRelevantFile(psiFile, type)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAllProjectPorkFiles(project: Project): List<PorkReferenceRelevantFile> {
|
||||||
|
val psiManager = PsiManager.getInstance(project)
|
||||||
|
val porkVirtualFiles = FilenameIndex.getAllFilesByExt(project, "pork")
|
||||||
|
return porkVirtualFiles.mapNotNull { virtualFile ->
|
||||||
|
psiManager.findFile(virtualFile)
|
||||||
|
}.map { PorkReferenceRelevantFile(it, PorkRelevantFileType.Local) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findPorkStdDirectory(project: Project): VirtualFile? = if (isPorkItself(project)) {
|
||||||
|
project.guessProjectDir()?.findDirectory("stdlib/src/main/pork")
|
||||||
|
} else {
|
||||||
|
project.guessProjectDir()?.fileSystem?.findFileByPath(
|
||||||
|
"/opt/pork/std"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAllPorkStdFiles(project: Project): List<PorkReferenceRelevantFile> {
|
||||||
|
val stdDirectoryPath = findPorkStdDirectory(project) ?: return emptyList()
|
||||||
|
|
||||||
|
val psiManager = PsiManager.getInstance(project)
|
||||||
|
val stdPorkFiles = mutableListOf<PorkReferenceRelevantFile>()
|
||||||
|
VfsUtilCore.iterateChildrenRecursively(stdDirectoryPath, VirtualFileFilter.ALL) { file ->
|
||||||
|
if (file.extension == "pork") {
|
||||||
|
val psiFile = psiManager.findFile(file)
|
||||||
|
if (psiFile != null) {
|
||||||
|
stdPorkFiles.add(PorkReferenceRelevantFile(psiFile, PorkRelevantFileType.Std))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
return stdPorkFiles
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isPorkItself(project: Project): Boolean {
|
||||||
|
if (project.name != "pork") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val projectDirectory = project.guessProjectDir() ?: return false
|
||||||
|
|
||||||
|
val prelude = projectDirectory.findFileOrDirectory(
|
||||||
|
"stdlib/src/main/pork/lang/prelude.pork"
|
||||||
|
)
|
||||||
|
return prelude != null && prelude.isFile
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findAllCandidates(internalPorkElement: PorkElement, name: String? = null): List<PorkElement> =
|
||||||
|
listOf(
|
||||||
|
findAnyLocals(internalPorkElement, name),
|
||||||
|
findAnyDefinitions(internalPorkElement.containingFile, name)
|
||||||
|
).flatten()
|
||||||
|
|
||||||
|
fun findAnyLocals(internalPorkElement: PorkElement, name: String? = null): List<PorkElement> {
|
||||||
|
val functionDefinitionElement = PsiTreeUtil.getParentOfType(
|
||||||
|
internalPorkElement,
|
||||||
|
FunctionDefinitionElement::class.java
|
||||||
|
) ?: return emptyList()
|
||||||
|
val locals = mutableListOf<PorkElement>()
|
||||||
|
|
||||||
|
fun check(localCandidate: PsiElement, upward: Boolean) {
|
||||||
|
if (localCandidate is BlockElement && !upward) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localCandidate is ArgumentSpecElement ||
|
||||||
|
localCandidate is LetAssignmentElement ||
|
||||||
|
localCandidate is VarAssignmentElement) {
|
||||||
|
locals.add(localCandidate as PorkElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localCandidate is ForInElement) {
|
||||||
|
val forInItem = localCandidate.childrenOfType<ForInItemElement>().firstOrNull()
|
||||||
|
if (forInItem != null) {
|
||||||
|
locals.add(forInItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
localCandidate.children.forEach { check(it, false) }
|
||||||
|
}
|
||||||
|
|
||||||
|
PsiTreeUtil.treeWalkUp(internalPorkElement, functionDefinitionElement) { _, localCandidate ->
|
||||||
|
if (localCandidate != null) {
|
||||||
|
if (internalPorkElement == functionDefinitionElement) {
|
||||||
|
return@treeWalkUp true
|
||||||
|
}
|
||||||
|
check(localCandidate, true)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
val argumentSpecElements = functionDefinitionElement.childrenOfType<ArgumentSpecElement>()
|
||||||
|
locals.addAll(argumentSpecElements)
|
||||||
|
val finalLocals = locals.distinctBy { it.textRange }
|
||||||
|
return finalLocals.filter { if (name != null) it.name == name else true }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findAnyDefinitions(containingFile: PsiFile, name: String? = null): List<PorkElement> {
|
||||||
|
val foundDefinitions = mutableListOf<PorkNamedElement>()
|
||||||
|
for (file in getRelevantFiles(containingFile)) {
|
||||||
|
val definitions = PsiTreeUtil.collectElements(file.file) { element ->
|
||||||
|
element is FunctionDefinitionElement ||
|
||||||
|
element is LetDefinitionElement
|
||||||
|
}.filterIsInstance<PorkNamedElement>()
|
||||||
|
if (name != null) {
|
||||||
|
val fileFoundDefinition = definitions.firstOrNull {
|
||||||
|
it.name == name
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileFoundDefinition != null) {
|
||||||
|
foundDefinitions.add(fileFoundDefinition)
|
||||||
|
return foundDefinitions
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
foundDefinitions.addAll(definitions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return foundDefinitions
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package gay.pizza.pork.idea.resolution
|
||||||
|
|
||||||
|
enum class PorkRelevantFileType {
|
||||||
|
Self,
|
||||||
|
Local,
|
||||||
|
Std
|
||||||
|
}
|
@ -26,6 +26,15 @@
|
|||||||
<lang.elementManipulator
|
<lang.elementManipulator
|
||||||
implementationClass="gay.pizza.pork.idea.PorkElementManipulator"
|
implementationClass="gay.pizza.pork.idea.PorkElementManipulator"
|
||||||
forClass="gay.pizza.pork.idea.psi.gen.PorkElement"/>
|
forClass="gay.pizza.pork.idea.psi.gen.PorkElement"/>
|
||||||
|
<codeInsight.parameterNameHints
|
||||||
|
language="Pork"
|
||||||
|
implementationClass="gay.pizza.pork.idea.PorkInlayParameterHintsProvider"/>
|
||||||
|
<lang.quoteHandler
|
||||||
|
language="Pork"
|
||||||
|
implementationClass="gay.pizza.pork.idea.PorkQuoteHandler"/>
|
||||||
|
<!-- <codeInsight.parameterInfo
|
||||||
|
language="Pork"
|
||||||
|
implementationClass="gay.pizza.pork.idea.PorkParameterInfoHandler"/>-->
|
||||||
<psi.declarationProvider implementation="gay.pizza.pork.idea.PorkSymbolDeclarationProvider"/>
|
<psi.declarationProvider implementation="gay.pizza.pork.idea.PorkSymbolDeclarationProvider"/>
|
||||||
</extensions>
|
</extensions>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user