mirror of
https://github.com/GayPizzaSpecifications/pork.git
synced 2025-08-02 12:50:55 +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
|
||||
- name: elseBlock
|
||||
type: Block?
|
||||
ImportPath:
|
||||
parent: Node
|
||||
referencedElementValue: components
|
||||
referencedElementType: CompilationUnit
|
||||
values:
|
||||
- name: components
|
||||
type: List<Symbol>
|
||||
ImportDeclaration:
|
||||
parent: Declaration
|
||||
values:
|
||||
- name: form
|
||||
type: Symbol
|
||||
- name: components
|
||||
type: List<Symbol>
|
||||
- name: path
|
||||
type: ImportPath
|
||||
IntegerLiteral:
|
||||
parent: Expression
|
||||
values:
|
||||
|
@ -18,6 +18,7 @@ digraph A {
|
||||
type_FunctionDefinition [shape=box,label="FunctionDefinition"]
|
||||
type_LetDefinition [shape=box,label="LetDefinition"]
|
||||
type_If [shape=box,label="If"]
|
||||
type_ImportPath [shape=box,label="ImportPath"]
|
||||
type_ImportDeclaration [shape=box,label="ImportDeclaration"]
|
||||
type_IntegerLiteral [shape=box,label="IntegerLiteral"]
|
||||
type_LongLiteral [shape=box,label="LongLiteral"]
|
||||
@ -45,6 +46,7 @@ digraph A {
|
||||
type_Node -> type_Block
|
||||
type_Node -> type_CompilationUnit
|
||||
type_Node -> type_ArgumentSpec
|
||||
type_Node -> type_ImportPath
|
||||
type_Node -> type_ForInItem
|
||||
type_Node -> type_Native
|
||||
type_Expression -> type_LetAssignment
|
||||
@ -98,7 +100,9 @@ digraph A {
|
||||
type_LetDefinition -> type_Expression [style=dotted]
|
||||
type_If -> type_Expression [style=dotted]
|
||||
type_If -> type_Block [style=dotted]
|
||||
type_ImportPath -> type_Symbol [style=dotted]
|
||||
type_ImportDeclaration -> type_Symbol [style=dotted]
|
||||
type_ImportDeclaration -> type_ImportPath [style=dotted]
|
||||
type_ListLiteral -> type_Expression [style=dotted]
|
||||
type_Parentheses -> type_Expression [style=dotted]
|
||||
type_PrefixOperation -> type_PrefixOperator [style=dotted]
|
||||
|
@ -6,23 +6,23 @@ import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@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 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 =
|
||||
visitor.visitImportDeclaration(this)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other !is ImportDeclaration) return false
|
||||
return other.form == form && other.components == components
|
||||
return other.form == form && other.path == path
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = form.hashCode()
|
||||
result = 31 * result + components.hashCode()
|
||||
result = 31 * result + path.hashCode()
|
||||
result = 31 * result + type.hashCode()
|
||||
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 =
|
||||
handle(node)
|
||||
|
||||
override fun visitImportPath(node: ImportPath): Unit =
|
||||
handle(node)
|
||||
|
||||
override fun visitIndexedBy(node: IndexedBy): Unit =
|
||||
handle(node)
|
||||
|
||||
|
@ -34,6 +34,8 @@ interface NodeParser {
|
||||
|
||||
fun parseImportDeclaration(): ImportDeclaration
|
||||
|
||||
fun parseImportPath(): ImportPath
|
||||
|
||||
fun parseIndexedBy(): IndexedBy
|
||||
|
||||
fun parseInfixOperation(): InfixOperation
|
||||
|
@ -19,6 +19,7 @@ fun NodeParser.parse(type: NodeType): Node =
|
||||
NodeType.FunctionDefinition -> parseFunctionDefinition()
|
||||
NodeType.LetDefinition -> parseLetDefinition()
|
||||
NodeType.If -> parseIf()
|
||||
NodeType.ImportPath -> parseImportPath()
|
||||
NodeType.ImportDeclaration -> parseImportDeclaration()
|
||||
NodeType.IntegerLiteral -> parseIntegerLiteral()
|
||||
NodeType.LongLiteral -> parseLongLiteral()
|
||||
|
@ -19,6 +19,7 @@ enum class NodeType(val parent: NodeType? = null) {
|
||||
FunctionDefinition(Definition),
|
||||
If(Expression),
|
||||
ImportDeclaration(Declaration),
|
||||
ImportPath(Node),
|
||||
IndexedBy(Expression),
|
||||
InfixOperation(Expression),
|
||||
IntegerLiteral(Expression),
|
||||
|
@ -28,6 +28,8 @@ interface NodeVisitor<T> {
|
||||
|
||||
fun visitImportDeclaration(node: ImportDeclaration): T
|
||||
|
||||
fun visitImportPath(node: ImportPath): T
|
||||
|
||||
fun visitIndexedBy(node: IndexedBy): T
|
||||
|
||||
fun visitInfixOperation(node: InfixOperation): T
|
||||
|
@ -16,6 +16,7 @@ fun <T> NodeVisitor<T>.visit(node: Node): T =
|
||||
is FunctionDefinition -> visitFunctionDefinition(node)
|
||||
is LetDefinition -> visitLetDefinition(node)
|
||||
is If -> visitIf(node)
|
||||
is ImportPath -> visitImportPath(node)
|
||||
is ImportDeclaration -> visitImportDeclaration(node)
|
||||
is IntegerLiteral -> visitIntegerLiteral(node)
|
||||
is LongLiteral -> visitLongLiteral(node)
|
||||
|
@ -54,7 +54,7 @@ class CompilationUnitContext(
|
||||
}
|
||||
|
||||
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 evaluationContext = evaluator.context(importLocator)
|
||||
internalScope.inherit(evaluationContext.externalScope)
|
||||
@ -67,9 +67,11 @@ class CompilationUnitContext(
|
||||
companion object {
|
||||
private val preludeImport = ImportDeclaration(
|
||||
Symbol("std"),
|
||||
listOf(
|
||||
Symbol("lang"),
|
||||
Symbol("prelude")
|
||||
ImportPath(
|
||||
listOf(
|
||||
Symbol("lang"),
|
||||
Symbol("prelude")
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -391,6 +391,10 @@ class EvaluationVisitor(root: Scope, val stack: CallStack) : NodeVisitor<Any> {
|
||||
topLevelUsedError("ImportDeclaration", "CompilationUnitContext")
|
||||
}
|
||||
|
||||
override fun visitImportPath(node: ImportPath): Any {
|
||||
topLevelUsedError("ImportPath", "CompilationUnitContext")
|
||||
}
|
||||
|
||||
override fun visitIndexedBy(node: IndexedBy): Any {
|
||||
val value = node.expression.visit(this)
|
||||
val index = node.index.visit(this)
|
||||
|
@ -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.definitions.map { it.text }, node.arguments)
|
||||
nativeFunctionProvider.provideNativeFunction(native.definitions.map { it.text }, node.arguments, compilationUnitContext)
|
||||
}
|
||||
|
||||
private val nativeCached by lazy { resolveMaybeNative() }
|
||||
|
@ -11,7 +11,15 @@ class InternalNativeProvider(val quiet: Boolean = false) : NativeProvider {
|
||||
"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]
|
||||
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
|
||||
|
||||
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() {
|
||||
let pointer = malloc(8192)
|
||||
println(pointer)
|
||||
free(pointer)
|
||||
let time = ffiStructAllocate(timeval)
|
||||
let zone = ffiStructAllocate(timezone)
|
||||
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 func SDL_PollEvent(event)
|
||||
native ffi "SDL2" "int SDL_PollEvent(struct SDL_Event*)"
|
||||
|
||||
export func SDL_CreateRenderer(window, index, flags)
|
||||
native ffi "SDL2" "void* SDL_CreateRenderer(void*, int, unsigned int)"
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
package gay.pizza.pork.ffi
|
||||
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.*
|
||||
|
||||
object FfiMacPlatform : FfiPlatform {
|
||||
@ -8,18 +7,23 @@ object FfiMacPlatform : FfiPlatform {
|
||||
"/Library/Frameworks"
|
||||
)
|
||||
|
||||
override fun findLibrary(name: String): Path? {
|
||||
override fun findLibrary(name: String): String? {
|
||||
val frameworksToCheck = frameworksDirectories.map { frameworkDirectory ->
|
||||
Path("$frameworkDirectory/$name.framework/$name")
|
||||
}
|
||||
for (framework in frameworksToCheck) {
|
||||
if (!framework.exists()) continue
|
||||
return if (framework.isSymbolicLink()) {
|
||||
return framework.parent.resolve(framework.readSymbolicLink()).absolute()
|
||||
return framework.parent.resolve(framework.readSymbolicLink()).absolutePathString()
|
||||
} else {
|
||||
framework.absolute()
|
||||
framework.absolutePathString()
|
||||
}
|
||||
}
|
||||
|
||||
if (name == "c") {
|
||||
return "libSystem.dylib"
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
@ -3,104 +3,153 @@ package gay.pizza.pork.ffi
|
||||
import com.kenai.jffi.*
|
||||
import com.kenai.jffi.Function
|
||||
import gay.pizza.pork.ast.gen.ArgumentSpec
|
||||
import gay.pizza.pork.evaluator.CallableFunction
|
||||
import gay.pizza.pork.evaluator.NativeProvider
|
||||
import gay.pizza.pork.evaluator.None
|
||||
import java.nio.file.Path
|
||||
import gay.pizza.pork.evaluator.*
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.absolutePathString
|
||||
import kotlin.io.path.exists
|
||||
|
||||
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 functionAddress = lookupSymbol(functionDefinition)
|
||||
|
||||
val ffiTypeRegistry = rootTypeRegistry.fork()
|
||||
|
||||
addStructDefs(ffiTypeRegistry, functionDefinition.parameters, inside)
|
||||
|
||||
val parameters = functionDefinition.parameters.map { id ->
|
||||
ffiTypeRegistry.lookup(id) ?: throw RuntimeException("Unknown ffi type: $id")
|
||||
ffiTypeRegistry.required(id)
|
||||
}
|
||||
|
||||
val returnTypeId = functionDefinition.returnType
|
||||
val returnType = ffiTypeRegistry.lookup(returnTypeId) ?:
|
||||
throw RuntimeException("Unknown ffi return type: $returnTypeId")
|
||||
val returnType = ffiTypeRegistry.required(returnTypeId)
|
||||
val returnTypeFfi = typeConversion(returnType)
|
||||
val parameterArray = parameters.map { typeConversion(it) }.toTypedArray()
|
||||
val function = Function(functionAddress, returnTypeFfi, *parameterArray)
|
||||
val context = function.callContext
|
||||
val invoker = Invoker.getInstance()
|
||||
return CallableFunction { functionArguments, _ ->
|
||||
val buffer = HeapInvocationBuffer(context)
|
||||
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 {
|
||||
val buffer = buildArgumentList(
|
||||
context,
|
||||
arguments,
|
||||
functionArguments,
|
||||
ffiTypeRegistry,
|
||||
functionDefinition,
|
||||
freeStringList
|
||||
)
|
||||
return@CallableFunction invoke(invoker, function, buffer, returnType)
|
||||
} 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 {
|
||||
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")
|
||||
val functionAddress = library.getSymbolAddress(functionDefinition.function)
|
||||
if (functionAddress == 0L) {
|
||||
throw RuntimeException(
|
||||
"Failed to find symbol ${functionDefinition.function} in " +
|
||||
"library ${actualLibraryPath.absolutePathString()}")
|
||||
"library $actualLibraryPath")
|
||||
}
|
||||
return functionAddress
|
||||
}
|
||||
|
||||
private 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
|
||||
else -> throw RuntimeException("Unknown ffi type: $type")
|
||||
}
|
||||
|
||||
private fun findLibraryPath(name: String): Path {
|
||||
private fun findLibraryPath(name: String): String {
|
||||
val initialPath = Path(name)
|
||||
if (initialPath.exists()) {
|
||||
return initialPath
|
||||
return initialPath.absolutePathString()
|
||||
}
|
||||
return FfiPlatforms.current.platform.findLibrary(name)
|
||||
?: throw RuntimeException("Unable to find library: $name")
|
||||
return FfiPlatforms.current.platform.findLibrary(name) ?: name
|
||||
}
|
||||
|
||||
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)
|
||||
else -> throw RuntimeException("Unsupported ffi return type: $type")
|
||||
} ?: 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
|
||||
|
||||
import java.nio.file.Path
|
||||
|
||||
enum class FfiPlatforms(val id: String, val platform: FfiPlatform) {
|
||||
Mac("macOS", FfiMacPlatform),
|
||||
Windows("Windows", FfiWindowsPlatform),
|
||||
@ -20,5 +18,5 @@ enum class FfiPlatforms(val id: String, val platform: FfiPlatform) {
|
||||
}
|
||||
|
||||
interface FfiPlatform {
|
||||
fun findLibrary(name: String): Path?
|
||||
fun findLibrary(name: String): String?
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package gay.pizza.pork.ffi
|
||||
|
||||
import com.kenai.jffi.InvocationBuffer
|
||||
import com.kenai.jffi.MemoryIO
|
||||
import gay.pizza.pork.evaluator.None
|
||||
|
||||
enum class FfiPrimitiveType(
|
||||
@ -89,6 +90,21 @@ enum class FfiPrimitiveType(
|
||||
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 {
|
||||
fun push(buffer: InvocationBuffer, value: Any): Unit = when (value) {
|
||||
is kotlin.Byte -> buffer.putByte(value.toInt())
|
||||
|
@ -1,36 +1,61 @@
|
||||
package gay.pizza.pork.ffi
|
||||
|
||||
import com.kenai.jffi.InvocationBuffer
|
||||
import com.kenai.jffi.MemoryIO
|
||||
import com.kenai.jffi.Struct
|
||||
import gay.pizza.pork.evaluator.None
|
||||
import java.util.*
|
||||
|
||||
class FfiStruct : FfiType {
|
||||
private val fields = TreeMap<String, FfiStructField>()
|
||||
class FfiStruct(val ffiTypeRegistry: FfiTypeRegistry) : FfiType {
|
||||
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)
|
||||
internalStructType = null
|
||||
internalTypes = null
|
||||
}
|
||||
|
||||
override val size: Long
|
||||
get() = fields.values.sumOf { it.type.size }
|
||||
get() = ffiStruct.size().toLong()
|
||||
|
||||
override fun put(buffer: InvocationBuffer, value: Any?) {
|
||||
when (value) {
|
||||
is Map<*, *> -> {
|
||||
for (field in fields.values) {
|
||||
val item = value[field.name] ?: None
|
||||
field.type.put(buffer, item)
|
||||
val itemType = ffiTypeRegistry.required(field.type)
|
||||
itemType.put(buffer, item)
|
||||
}
|
||||
}
|
||||
|
||||
is List<*> -> {
|
||||
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 -> {
|
||||
throw RuntimeException("Unknown value type: $value")
|
||||
}
|
||||
@ -40,4 +65,28 @@ class FfiStruct : FfiType {
|
||||
override fun value(ffi: Any?): Any {
|
||||
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 value(ffi: Any?): Any
|
||||
fun read(address: FfiAddress, offset: Int): Any
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
package gay.pizza.pork.ffi
|
||||
|
||||
class FfiTypeRegistry {
|
||||
class FfiTypeRegistry(val parent: FfiTypeRegistry? = null) {
|
||||
private val types = mutableMapOf<String, FfiType>()
|
||||
|
||||
init {
|
||||
fun registerPrimitiveTypes() {
|
||||
for (type in FfiPrimitiveType.entries) {
|
||||
add(type.id, type)
|
||||
}
|
||||
@ -12,7 +12,14 @@ class FfiTypeRegistry {
|
||||
|
||||
fun add(name: String, type: FfiType) {
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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.evaluator.CallableFunction
|
||||
import gay.pizza.pork.evaluator.CompilationUnitContext
|
||||
import gay.pizza.pork.evaluator.NativeProvider
|
||||
import gay.pizza.pork.evaluator.None
|
||||
import java.lang.invoke.MethodHandles
|
||||
@ -10,7 +11,11 @@ import java.lang.invoke.MethodType
|
||||
class JavaNativeProvider : NativeProvider {
|
||||
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 javaClass = lookupClass(functionDefinition.type)
|
||||
val returnTypeClass = lookupClass(functionDefinition.returnType)
|
||||
|
@ -23,7 +23,7 @@ class World(val importSource: ImportSource) {
|
||||
}
|
||||
val charSource = contentSource.loadAsCharSource(importLocator.path)
|
||||
val tokenizer = Tokenizer(charSource)
|
||||
val tokenStream = tokenizer.tokenize()
|
||||
val tokenStream = tokenizer.stream()
|
||||
val parser = Parser(TokenStreamSource(tokenStream), DiscardNodeAttribution)
|
||||
val unit = parser.parseCompilationUnit()
|
||||
internalUnits[stableKey] = unit
|
||||
@ -33,7 +33,7 @@ class World(val importSource: ImportSource) {
|
||||
private fun resolveAllImports(unit: CompilationUnit): Set<CompilationUnit> {
|
||||
val units = mutableSetOf<CompilationUnit>()
|
||||
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 importedUnit = loadOneUnit(importLocator)
|
||||
units.add(importedUnit)
|
||||
|
@ -23,7 +23,7 @@ abstract class Tool {
|
||||
get() = ImportLocator("local", rootFilePath())
|
||||
|
||||
fun tokenize(): TokenStream =
|
||||
Tokenizer(createCharSource()).tokenize()
|
||||
Tokenizer(createCharSource()).stream()
|
||||
|
||||
fun parse(attribution: NodeAttribution = DiscardNodeAttribution): CompilationUnit =
|
||||
Parser(TokenStreamSource(tokenize()), attribution).parseCompilationUnit()
|
||||
|
@ -1,5 +1,5 @@
|
||||
package gay.pizza.pork.parser
|
||||
|
||||
class BadCharacterError(val char: Char, sourceIndex: SourceIndex) : ParseError(
|
||||
"Failed to produce token for '${char}' at $sourceIndex"
|
||||
class BadCharacterError(val char: Char, sourceIndex: SourceIndex, state: TokenizerState) : ParseError(
|
||||
"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() {
|
||||
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()
|
||||
var expression = when (token.type) {
|
||||
TokenType.NumberLiteral -> parseNumberLiteral()
|
||||
TokenType.StringLiteral -> parseStringLiteral()
|
||||
TokenType.Quote -> parseStringLiteral()
|
||||
TokenType.True, TokenType.False -> parseBooleanLiteral()
|
||||
TokenType.LeftBracket -> parseListLiteral()
|
||||
TokenType.Let -> parseLetAssignment()
|
||||
@ -233,10 +233,14 @@ class Parser(source: TokenSource, attribution: NodeAttribution) :
|
||||
|
||||
override fun parseImportDeclaration(): ImportDeclaration = expect(NodeType.ImportDeclaration, TokenType.Import) {
|
||||
val form = parseSymbol()
|
||||
ImportDeclaration(form, parseImportPath())
|
||||
}
|
||||
|
||||
override fun parseImportPath(): ImportPath = guarded(NodeType.ImportPath) {
|
||||
val components = oneAndContinuedBy(TokenType.Dot) {
|
||||
parseSymbol()
|
||||
}
|
||||
ImportDeclaration(form, components)
|
||||
ImportPath(components)
|
||||
}
|
||||
|
||||
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) {
|
||||
val form = parseSymbol()
|
||||
val definitions = mutableListOf<StringLiteral>()
|
||||
while (peek(TokenType.StringLiteral)) {
|
||||
while (peek(TokenType.Quote)) {
|
||||
definitions.add(parseStringLiteral())
|
||||
}
|
||||
Native(form, definitions)
|
||||
@ -333,8 +337,11 @@ class Parser(source: TokenSource, attribution: NodeAttribution) :
|
||||
SetAssignment(symbol, value)
|
||||
}
|
||||
|
||||
override fun parseStringLiteral(): StringLiteral = expect(NodeType.StringLiteral, TokenType.StringLiteral) {
|
||||
val content = StringEscape.unescape(StringEscape.unquote(it.text))
|
||||
override fun parseStringLiteral(): StringLiteral = guarded(NodeType.StringLiteral) {
|
||||
expect(TokenType.Quote)
|
||||
val stringLiteralToken = expect(TokenType.StringLiteral)
|
||||
expect(TokenType.Quote)
|
||||
val content = StringEscape.unescape(stringLiteralToken.text)
|
||||
StringLiteral(content)
|
||||
}
|
||||
|
||||
|
@ -24,4 +24,13 @@ class ParserStackAnalysis(private val stack: Array<StackTraceElement>) {
|
||||
}
|
||||
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 ")
|
||||
visit(node.form)
|
||||
append(" ")
|
||||
visit(node.path)
|
||||
}
|
||||
|
||||
override fun visitImportPath(node: ImportPath) {
|
||||
for ((index, component) in node.components.withIndex()) {
|
||||
visit(component)
|
||||
if (index != node.components.size - 1) {
|
||||
|
@ -2,12 +2,7 @@ package gay.pizza.pork.parser
|
||||
|
||||
object StringCharConsumer : CharConsumer {
|
||||
override fun consume(type: TokenType, tokenizer: Tokenizer): String? {
|
||||
if (!tokenizer.peek("\"")) {
|
||||
return null
|
||||
}
|
||||
|
||||
val buffer = StringBuilder()
|
||||
buffer.append(tokenizer.source.next())
|
||||
var escape = false
|
||||
while (true) {
|
||||
val char = tokenizer.source.peek()
|
||||
@ -16,12 +11,14 @@ object StringCharConsumer : CharConsumer {
|
||||
throw UnterminatedTokenError("String", tokenizer.source.currentSourceIndex())
|
||||
}
|
||||
|
||||
if (char == '"' && !escape) {
|
||||
break
|
||||
}
|
||||
|
||||
buffer.append(tokenizer.source.next())
|
||||
|
||||
if (char == '\\') {
|
||||
escape = true
|
||||
} else if (char == '"' && !escape) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return buffer.toString()
|
||||
|
@ -3,5 +3,4 @@ package gay.pizza.pork.parser
|
||||
object StringEscape {
|
||||
fun escape(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.TokenTypeProperty.AnyOf
|
||||
|
||||
enum class TokenType(vararg properties: TokenTypeProperty) {
|
||||
enum class TokenType(vararg val properties: TokenTypeProperty) {
|
||||
NumberLiteral(NumericLiteralFamily, CharMatch(CharMatcher.AnyOf(
|
||||
MatchRange('0'..'9'),
|
||||
NotAtIndex(0, MatchSingle('.'))
|
||||
@ -17,7 +17,8 @@ enum class TokenType(vararg properties: TokenTypeProperty) {
|
||||
MatchRange('0' .. '9'),
|
||||
MatchSingle('_')
|
||||
)), KeywordUpgrader),
|
||||
StringLiteral(StringLiteralFamily, CharConsume(StringCharConsumer)),
|
||||
Quote(StringLiteralFamily, SingleChar('"'), InsideStates(TokenizerState.Normal, TokenizerState.StringLiteralEnd)),
|
||||
StringLiteral(StringLiteralFamily, CharConsume(StringCharConsumer), InsideStates(TokenizerState.StringLiteralStart)),
|
||||
Equality(OperatorFamily),
|
||||
Inequality(ManyChars("!="), OperatorFamily),
|
||||
ExclamationPoint(SingleChar('!'), Promotion('=', Inequality)),
|
||||
@ -91,6 +92,11 @@ enum class TokenType(vararg properties: TokenTypeProperty) {
|
||||
val charConsume: CharConsume? = properties.filterIsInstance<CharConsume>().singleOrNull()
|
||||
val tokenUpgrader: TokenUpgrader? =
|
||||
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()
|
||||
|
||||
|
@ -5,6 +5,7 @@ interface TokenTypeProperty {
|
||||
class Promotion(val nextChar: Char, val type: TokenType) : TokenTypeProperty
|
||||
class ManyChars(val text: 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 CharConsume(val consumer: CharConsumer) : TokenTypeProperty
|
||||
open class TokenUpgrader(val maybeUpgrade: (Token) -> Token?) : TokenTypeProperty
|
||||
|
@ -2,73 +2,100 @@ package gay.pizza.pork.parser
|
||||
|
||||
class Tokenizer(source: CharSource) {
|
||||
val source: SourceIndexCharSource = SourceIndexCharSource(source)
|
||||
|
||||
private var startIndex: SourceIndex = SourceIndex.zero()
|
||||
private var state = TokenizerState.Normal
|
||||
|
||||
fun next(): Token {
|
||||
while (source.peek() != CharSource.EndOfFile) {
|
||||
startIndex = 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)
|
||||
private fun nextTokenOrNull(): Token? {
|
||||
if (source.peek() == CharSource.EndOfFile) {
|
||||
source.next()
|
||||
return Token.endOfFile(source.currentSourceIndex())
|
||||
}
|
||||
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>()
|
||||
while (true) {
|
||||
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.presentation.TargetPresentation
|
||||
import gay.pizza.pork.idea.psi.gen.PorkElement
|
||||
import gay.pizza.pork.idea.resolution.PorkReferenceResolution
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
data class PorkDeclarationSymbol(val module: String, val name: String) : Symbol, NavigatableSymbol {
|
||||
override fun createPointer(): Pointer<out Symbol> = Pointer { this }
|
||||
override fun getNavigationTargets(project: Project): MutableCollection<out NavigationTarget> {
|
||||
return PorkReferenceResolution.getAllProjectPorkFiles(project)
|
||||
.flatMap { PorkReferenceResolution.findAnyDefinitions(it) }
|
||||
.flatMap { PorkReferenceResolution.findAnyDefinitions(it.file) }
|
||||
.map { PorkNavigationTarget(it) }
|
||||
.toMutableList()
|
||||
}
|
||||
|
@ -35,6 +35,10 @@ object PorkElementTypes {
|
||||
elementTypeFor(TokenType.StringLiteral)
|
||||
)
|
||||
|
||||
val QuoteSet = TokenSet.create(
|
||||
elementTypeFor(TokenType.Quote)
|
||||
)
|
||||
|
||||
fun tokenTypeFor(elementType: IElementType): TokenType? =
|
||||
elementTypeToTokenType[elementType]
|
||||
|
||||
|
@ -10,4 +10,8 @@ class PorkFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, PorkL
|
||||
}
|
||||
|
||||
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? {
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
import com.intellij.psi.PsiFile
|
||||
import gay.pizza.pork.idea.PorkReferenceResolution
|
||||
import gay.pizza.pork.idea.resolution.PorkReferenceResolution
|
||||
import gay.pizza.pork.idea.psi.gen.PorkElement
|
||||
import gay.pizza.pork.idea.resolution.PorkReferenceRelevantFile
|
||||
|
||||
interface PorkReferencable {
|
||||
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> =
|
||||
listOf(findAnyLocals(name), findAnyDefinitions(name)).flatten()
|
||||
PorkReferenceResolution.findAllCandidates(internalPorkElement, name)
|
||||
|
||||
fun findAnyLocals(name: String? = null): List<PorkElement> =
|
||||
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.LetDefinition -> LetDefinitionElement(node)
|
||||
NodeType.If -> IfElement(node)
|
||||
NodeType.ImportPath -> ImportPathElement(node)
|
||||
NodeType.ImportDeclaration -> ImportDeclarationElement(node)
|
||||
NodeType.IntegerLiteral -> IntegerLiteralElement(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
|
||||
implementationClass="gay.pizza.pork.idea.PorkElementManipulator"
|
||||
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"/>
|
||||
</extensions>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user