pork: it's got it all, ffi, state machine tokenizer, and better IDE support

This commit is contained in:
Alex Zenla 2023-10-13 01:04:35 -07:00
parent d355fb3914
commit 5078f38f61
Signed by: alex
GPG Key ID: C0780728420EBFE5
58 changed files with 939 additions and 293 deletions

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@ -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()

View File

@ -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),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)"

View File

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

View File

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

View File

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

View File

@ -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())

View File

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

View File

@ -0,0 +1,3 @@
package gay.pizza.pork.ffi
data class FfiStructDefinition(val values: LinkedHashMap<String, String>)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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()

View File

@ -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"
) )

View File

@ -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()}"
} }

View File

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

View File

@ -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(", ")}"
}
} }

View File

@ -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) {

View File

@ -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()

View File

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

View File

@ -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()

View File

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

View File

@ -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()

View File

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

View 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"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,5 @@
package gay.pizza.pork.idea
import com.intellij.codeInsight.editorActions.SimpleTokenSetQuoteHandler
class PorkQuoteHandler : SimpleTokenSetQuoteHandler(PorkElementTypes.QuoteSet)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,5 @@
package gay.pizza.pork.idea.resolution
import com.intellij.psi.PsiFile
class PorkReferenceRelevantFile(val file: PsiFile, val type: PorkRelevantFileType)

View File

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

View File

@ -0,0 +1,7 @@
package gay.pizza.pork.idea.resolution
enum class PorkRelevantFileType {
Self,
Local,
Std
}

View File

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