From 5078f38f618fc4e80a3246b8165f60343acfd5dc Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Fri, 13 Oct 2023 01:04:35 -0700 Subject: [PATCH] pork: it's got it all, ffi, state machine tokenizer, and better IDE support --- ast/src/main/ast/pork.yml | 11 +- ast/src/main/graph/types.dot | 4 + .../pizza/pork/ast/gen/ImportDeclaration.kt | 8 +- .../gay/pizza/pork/ast/gen/ImportPath.kt | 28 +++ .../gay/pizza/pork/ast/gen/NodeCoalescer.kt | 3 + .../gay/pizza/pork/ast/gen/NodeParser.kt | 2 + .../pork/ast/gen/NodeParserExtensions.kt | 1 + .../kotlin/gay/pizza/pork/ast/gen/NodeType.kt | 1 + .../gay/pizza/pork/ast/gen/NodeVisitor.kt | 2 + .../pork/ast/gen/NodeVisitorExtensions.kt | 1 + .../pork/evaluator/CompilationUnitContext.kt | 10 +- .../pizza/pork/evaluator/EvaluationVisitor.kt | 4 + .../pizza/pork/evaluator/FunctionContext.kt | 2 +- .../pork/evaluator/InternalNativeProvider.kt | 10 +- .../pizza/pork/evaluator/NativeProvider.kt | 2 +- examples/ffi.pork | 24 +- examples/gameoflife/SDL2.pork | 3 + .../gay/pizza/pork/ffi/FfiMacPlatform.kt | 12 +- .../gay/pizza/pork/ffi/FfiNativeProvider.kt | 224 +++++++++++++----- .../kotlin/gay/pizza/pork/ffi/FfiPlatform.kt | 4 +- .../gay/pizza/pork/ffi/FfiPrimitiveType.kt | 16 ++ .../kotlin/gay/pizza/pork/ffi/FfiStruct.kt | 65 ++++- .../gay/pizza/pork/ffi/FfiStructDefinition.kt | 3 + .../main/kotlin/gay/pizza/pork/ffi/FfiType.kt | 1 + .../gay/pizza/pork/ffi/FfiTypeRegistry.kt | 13 +- .../gay/pizza/pork/ffi/FfiUnixPlatform.kt | 2 +- .../gay/pizza/pork/ffi/FfiWindowsPlatform.kt | 2 +- .../gay/pizza/pork/ffi/JavaNativeProvider.kt | 7 +- .../kotlin/gay/pizza/pork/frontend/World.kt | 4 +- .../kotlin/gay/pizza/pork/minimal/Tool.kt | 2 +- .../pizza/pork/parser/BadCharacterError.kt | 4 +- .../gay/pizza/pork/parser/ParseError.kt | 2 +- .../kotlin/gay/pizza/pork/parser/Parser.kt | 17 +- .../pizza/pork/parser/ParserStackAnalysis.kt | 9 + .../kotlin/gay/pizza/pork/parser/Printer.kt | 4 + .../pizza/pork/parser/StringCharConsumer.kt | 11 +- .../gay/pizza/pork/parser/StringEscape.kt | 1 - .../kotlin/gay/pizza/pork/parser/TokenType.kt | 10 +- .../pizza/pork/parser/TokenTypeProperty.kt | 1 + .../kotlin/gay/pizza/pork/parser/Tokenizer.kt | 151 +++++++----- .../gay/pizza/pork/parser/TokenizerState.kt | 12 + stdlib/src/main/pork/ffi/struct.pork | 11 + .../pizza/pork/idea/PorkDeclarationSymbol.kt | 3 +- .../gay/pizza/pork/idea/PorkElementTypes.kt | 4 + .../kotlin/gay/pizza/pork/idea/PorkFile.kt | 4 + .../idea/PorkInlayParameterHintsProvider.kt | 34 +++ .../pork/idea/PorkParameterInfoHandler.kt | 90 +++++++ .../gay/pizza/pork/idea/PorkQuoteHandler.kt | 5 + .../pork/idea/PorkReferenceResolution.kt | 109 --------- .../pizza/pork/idea/psi/PorkElementHelpers.kt | 8 +- .../pizza/pork/idea/psi/PorkFileReference.kt | 22 ++ .../pizza/pork/idea/psi/PorkReferencable.kt | 10 +- .../pork/idea/psi/gen/ImportPathElement.kt | 20 ++ .../pork/idea/psi/gen/PorkElementFactory.kt | 1 + .../resolution/PorkReferenceRelevantFile.kt | 5 + .../resolution/PorkReferenceResolution.kt | 197 +++++++++++++++ .../idea/resolution/PorkRelevantFileType.kt | 7 + .../src/main/resources/META-INF/plugin.xml | 9 + 58 files changed, 939 insertions(+), 293 deletions(-) create mode 100644 ast/src/main/kotlin/gay/pizza/pork/ast/gen/ImportPath.kt create mode 100644 ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiStructDefinition.kt create mode 100644 parser/src/main/kotlin/gay/pizza/pork/parser/TokenizerState.kt create mode 100644 stdlib/src/main/pork/ffi/struct.pork create mode 100644 support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkInlayParameterHintsProvider.kt create mode 100644 support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkParameterInfoHandler.kt create mode 100644 support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkQuoteHandler.kt delete mode 100644 support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkReferenceResolution.kt create mode 100644 support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkFileReference.kt create mode 100644 support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/gen/ImportPathElement.kt create mode 100644 support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/resolution/PorkReferenceRelevantFile.kt create mode 100644 support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/resolution/PorkReferenceResolution.kt create mode 100644 support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/resolution/PorkRelevantFileType.kt diff --git a/ast/src/main/ast/pork.yml b/ast/src/main/ast/pork.yml index e8d1cd8..76e763a 100644 --- a/ast/src/main/ast/pork.yml +++ b/ast/src/main/ast/pork.yml @@ -178,13 +178,20 @@ types: type: Block - name: elseBlock type: Block? + ImportPath: + parent: Node + referencedElementValue: components + referencedElementType: CompilationUnit + values: + - name: components + type: List ImportDeclaration: parent: Declaration values: - name: form type: Symbol - - name: components - type: List + - name: path + type: ImportPath IntegerLiteral: parent: Expression values: diff --git a/ast/src/main/graph/types.dot b/ast/src/main/graph/types.dot index b887e13..70d086e 100644 --- a/ast/src/main/graph/types.dot +++ b/ast/src/main/graph/types.dot @@ -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] diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/gen/ImportDeclaration.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/gen/ImportDeclaration.kt index afca23c..e9e1da9 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/gen/ImportDeclaration.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/gen/ImportDeclaration.kt @@ -6,23 +6,23 @@ import kotlinx.serialization.Serializable @Serializable @SerialName("importDeclaration") -class ImportDeclaration(val form: Symbol, val components: List) : Declaration() { +class ImportDeclaration(val form: Symbol, val path: ImportPath) : Declaration() { override val type: NodeType = NodeType.ImportDeclaration override fun visitChildren(visitor: NodeVisitor): List = - visitor.visitAll(listOf(form), components) + visitor.visitNodes(form, path) override fun visit(visitor: NodeVisitor): 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 } diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/gen/ImportPath.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/gen/ImportPath.kt new file mode 100644 index 0000000..b3ad540 --- /dev/null +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/gen/ImportPath.kt @@ -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) : Node() { + override val type: NodeType = NodeType.ImportPath + + override fun visitChildren(visitor: NodeVisitor): List = + visitor.visitAll(components) + + override fun visit(visitor: NodeVisitor): 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 + } +} diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/gen/NodeCoalescer.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/gen/NodeCoalescer.kt index 8afc004..11ae5de 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/gen/NodeCoalescer.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/gen/NodeCoalescer.kt @@ -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) diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/gen/NodeParser.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/gen/NodeParser.kt index e783996..5fb0c21 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/gen/NodeParser.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/gen/NodeParser.kt @@ -34,6 +34,8 @@ interface NodeParser { fun parseImportDeclaration(): ImportDeclaration + fun parseImportPath(): ImportPath + fun parseIndexedBy(): IndexedBy fun parseInfixOperation(): InfixOperation diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/gen/NodeParserExtensions.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/gen/NodeParserExtensions.kt index 8effba9..fd027c5 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/gen/NodeParserExtensions.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/gen/NodeParserExtensions.kt @@ -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() diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/gen/NodeType.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/gen/NodeType.kt index bdcffd3..3e2ca7f 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/gen/NodeType.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/gen/NodeType.kt @@ -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), diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/gen/NodeVisitor.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/gen/NodeVisitor.kt index ff1e163..9a8b156 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/gen/NodeVisitor.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/gen/NodeVisitor.kt @@ -28,6 +28,8 @@ interface NodeVisitor { fun visitImportDeclaration(node: ImportDeclaration): T + fun visitImportPath(node: ImportPath): T + fun visitIndexedBy(node: IndexedBy): T fun visitInfixOperation(node: InfixOperation): T diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/gen/NodeVisitorExtensions.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/gen/NodeVisitorExtensions.kt index 3d2dfb0..5b71861 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/gen/NodeVisitorExtensions.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/gen/NodeVisitorExtensions.kt @@ -16,6 +16,7 @@ fun NodeVisitor.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) diff --git a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/CompilationUnitContext.kt b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/CompilationUnitContext.kt index 0afdc6d..a4c5f3c 100644 --- a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/CompilationUnitContext.kt +++ b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/CompilationUnitContext.kt @@ -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") + ) ) ) } diff --git a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/EvaluationVisitor.kt b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/EvaluationVisitor.kt index 0133c38..b6290cf 100644 --- a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/EvaluationVisitor.kt +++ b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/EvaluationVisitor.kt @@ -391,6 +391,10 @@ class EvaluationVisitor(root: Scope, val stack: CallStack) : NodeVisitor { 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) diff --git a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/FunctionContext.kt b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/FunctionContext.kt index 002e158..832097a 100644 --- a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/FunctionContext.kt +++ b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/FunctionContext.kt @@ -11,7 +11,7 @@ class FunctionContext(val compilationUnitContext: CompilationUnitContext, val no val native = node.native!! val nativeFunctionProvider = compilationUnitContext.evaluator.nativeFunctionProvider(native.form.id) - nativeFunctionProvider.provideNativeFunction(native.definitions.map { it.text }, node.arguments) + nativeFunctionProvider.provideNativeFunction(native.definitions.map { it.text }, node.arguments, compilationUnitContext) } private val nativeCached by lazy { resolveMaybeNative() } diff --git a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/InternalNativeProvider.kt b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/InternalNativeProvider.kt index 2d34e0a..c0c04ef 100644 --- a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/InternalNativeProvider.kt +++ b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/InternalNativeProvider.kt @@ -11,7 +11,15 @@ class InternalNativeProvider(val quiet: Boolean = false) : NativeProvider { "listInitWith" to CallableFunction(::listInitWith) ) - override fun provideNativeFunction(definitions: List, arguments: List): CallableFunction { + fun add(name: String, function: CallableFunction) { + functions[name] = function + } + + override fun provideNativeFunction( + definitions: List, + arguments: List, + inside: CompilationUnitContext + ): CallableFunction { val definition = definitions[0] return functions[definition] ?: throw RuntimeException("Unknown Internal Function: $definition") } diff --git a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/NativeProvider.kt b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/NativeProvider.kt index 165a218..4ca6380 100644 --- a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/NativeProvider.kt +++ b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/NativeProvider.kt @@ -3,5 +3,5 @@ package gay.pizza.pork.evaluator import gay.pizza.pork.ast.gen.ArgumentSpec interface NativeProvider { - fun provideNativeFunction(definitions: List, arguments: List): CallableFunction + fun provideNativeFunction(definitions: List, arguments: List, inside: CompilationUnitContext): CallableFunction } diff --git a/examples/ffi.pork b/examples/ffi.pork index c2a1af3..4bcc244 100644 --- a/examples/ffi.pork +++ b/examples/ffi.pork @@ -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) } diff --git a/examples/gameoflife/SDL2.pork b/examples/gameoflife/SDL2.pork index 91f8e91..1161d0d 100644 --- a/examples/gameoflife/SDL2.pork +++ b/examples/gameoflife/SDL2.pork @@ -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)" diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiMacPlatform.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiMacPlatform.kt index 9a24c22..3f614c6 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiMacPlatform.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiMacPlatform.kt @@ -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 } } diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiNativeProvider.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiNativeProvider.kt index ebabfca..aa37f98 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiNativeProvider.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiNativeProvider.kt @@ -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 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, + arguments: List, + 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, arguments: List): 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() - 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, 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, + functionArguments: List, + ffiTypeRegistry: FfiTypeRegistry, + functionDefinition: FfiFunctionDefinition, + freeStringList: MutableList + ): 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() + 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") + } + } } diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiPlatform.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiPlatform.kt index c60a82e..c50e027 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiPlatform.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiPlatform.kt @@ -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? } diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiPrimitiveType.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiPrimitiveType.kt index 627140c..e46e499 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiPrimitiveType.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiPrimitiveType.kt @@ -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()) diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiStruct.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiStruct.kt index ea9e74e..ed1965b 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiStruct.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiStruct.kt @@ -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() +class FfiStruct(val ffiTypeRegistry: FfiTypeRegistry) : FfiType { + private val fields = LinkedHashMap() + 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? = null + val ffiTypes: List + 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()) + } } diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiStructDefinition.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiStructDefinition.kt new file mode 100644 index 0000000..747fbf7 --- /dev/null +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiStructDefinition.kt @@ -0,0 +1,3 @@ +package gay.pizza.pork.ffi + +data class FfiStructDefinition(val values: LinkedHashMap) diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiType.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiType.kt index 28604f9..4b3fd05 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiType.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiType.kt @@ -7,4 +7,5 @@ interface FfiType { fun put(buffer: InvocationBuffer, value: Any?) fun value(ffi: Any?): Any + fun read(address: FfiAddress, offset: Int): Any } diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiTypeRegistry.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiTypeRegistry.kt index 486ed0f..45d4d73 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiTypeRegistry.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiTypeRegistry.kt @@ -1,9 +1,9 @@ package gay.pizza.pork.ffi -class FfiTypeRegistry { +class FfiTypeRegistry(val parent: FfiTypeRegistry? = null) { private val types = mutableMapOf() - 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) } diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiUnixPlatform.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiUnixPlatform.kt index 910c80d..398390b 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiUnixPlatform.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiUnixPlatform.kt @@ -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 } diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiWindowsPlatform.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiWindowsPlatform.kt index 77a8409..2e95cc4 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiWindowsPlatform.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiWindowsPlatform.kt @@ -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 } diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaNativeProvider.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaNativeProvider.kt index f226b69..4dfaf38 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaNativeProvider.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaNativeProvider.kt @@ -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, arguments: List): CallableFunction { + override fun provideNativeFunction( + definitions: List, + arguments: List, + inside: CompilationUnitContext + ): CallableFunction { val functionDefinition = JavaFunctionDefinition.parse(definitions) val javaClass = lookupClass(functionDefinition.type) val returnTypeClass = lookupClass(functionDefinition.returnType) diff --git a/frontend/src/main/kotlin/gay/pizza/pork/frontend/World.kt b/frontend/src/main/kotlin/gay/pizza/pork/frontend/World.kt index a5354ea..4d7930c 100644 --- a/frontend/src/main/kotlin/gay/pizza/pork/frontend/World.kt +++ b/frontend/src/main/kotlin/gay/pizza/pork/frontend/World.kt @@ -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 { val units = mutableSetOf() for (declaration in unit.declarations.filterIsInstance()) { - 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) diff --git a/minimal/src/main/kotlin/gay/pizza/pork/minimal/Tool.kt b/minimal/src/main/kotlin/gay/pizza/pork/minimal/Tool.kt index 9aab711..d98c8a3 100644 --- a/minimal/src/main/kotlin/gay/pizza/pork/minimal/Tool.kt +++ b/minimal/src/main/kotlin/gay/pizza/pork/minimal/Tool.kt @@ -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() diff --git a/parser/src/main/kotlin/gay/pizza/pork/parser/BadCharacterError.kt b/parser/src/main/kotlin/gay/pizza/pork/parser/BadCharacterError.kt index 4674ade..d254c2c 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/BadCharacterError.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/BadCharacterError.kt @@ -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" ) diff --git a/parser/src/main/kotlin/gay/pizza/pork/parser/ParseError.kt b/parser/src/main/kotlin/gay/pizza/pork/parser/ParseError.kt index ab529ae..a587736 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/ParseError.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/ParseError.kt @@ -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()}" } diff --git a/parser/src/main/kotlin/gay/pizza/pork/parser/Parser.kt b/parser/src/main/kotlin/gay/pizza/pork/parser/Parser.kt index 282eae5..e42e3d6 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/Parser.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/Parser.kt @@ -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() - 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) } diff --git a/parser/src/main/kotlin/gay/pizza/pork/parser/ParserStackAnalysis.kt b/parser/src/main/kotlin/gay/pizza/pork/parser/ParserStackAnalysis.kt index 3770f94..beff196 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/ParserStackAnalysis.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/ParserStackAnalysis.kt @@ -24,4 +24,13 @@ class ParserStackAnalysis(private val stack: Array) { } return parseDescentPaths.reversed() } + + fun buildDescentPathAddendum(): String { + val descentPath = findDescentPath() + if (descentPath.isEmpty()) { + return "" + } + + return "\nParser descent path: ${descentPath.joinToString(", ")}" + } } diff --git a/parser/src/main/kotlin/gay/pizza/pork/parser/Printer.kt b/parser/src/main/kotlin/gay/pizza/pork/parser/Printer.kt index 2da75a5..9ee5f94 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/Printer.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/Printer.kt @@ -238,6 +238,10 @@ class Printer(buffer: StringBuilder) : NodeVisitor { 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) { diff --git a/parser/src/main/kotlin/gay/pizza/pork/parser/StringCharConsumer.kt b/parser/src/main/kotlin/gay/pizza/pork/parser/StringCharConsumer.kt index 933c0bf..ee66824 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/StringCharConsumer.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/StringCharConsumer.kt @@ -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() diff --git a/parser/src/main/kotlin/gay/pizza/pork/parser/StringEscape.kt b/parser/src/main/kotlin/gay/pizza/pork/parser/StringEscape.kt index 74089e6..34630c1 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/StringEscape.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/StringEscape.kt @@ -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) } diff --git a/parser/src/main/kotlin/gay/pizza/pork/parser/TokenType.kt b/parser/src/main/kotlin/gay/pizza/pork/parser/TokenType.kt index ca5db56..1f89626 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/TokenType.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/TokenType.kt @@ -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().singleOrNull() val tokenUpgrader: TokenUpgrader? = properties.filterIsInstance().singleOrNull() + val validStates: List by lazy { + properties + .filterIsInstance() + .singleOrNull()?.states?.toList() ?: listOf(TokenizerState.Normal) + } val simpleWantString: String? = manyChars?.text ?: singleChar?.char?.toString() diff --git a/parser/src/main/kotlin/gay/pizza/pork/parser/TokenTypeProperty.kt b/parser/src/main/kotlin/gay/pizza/pork/parser/TokenTypeProperty.kt index 64f40aa..878f79d 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/TokenTypeProperty.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/TokenTypeProperty.kt @@ -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 diff --git a/parser/src/main/kotlin/gay/pizza/pork/parser/Tokenizer.kt b/parser/src/main/kotlin/gay/pizza/pork/parser/Tokenizer.kt index 402ae8c..a4824e1 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/Tokenizer.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/Tokenizer.kt @@ -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() while (true) { val token = next() diff --git a/parser/src/main/kotlin/gay/pizza/pork/parser/TokenizerState.kt b/parser/src/main/kotlin/gay/pizza/pork/parser/TokenizerState.kt new file mode 100644 index 0000000..3f0f08a --- /dev/null +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/TokenizerState.kt @@ -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() } + } +} diff --git a/stdlib/src/main/pork/ffi/struct.pork b/stdlib/src/main/pork/ffi/struct.pork new file mode 100644 index 0000000..bc2f59a --- /dev/null +++ b/stdlib/src/main/pork/ffi/struct.pork @@ -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" diff --git a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkDeclarationSymbol.kt b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkDeclarationSymbol.kt index b9407b4..146557e 100644 --- a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkDeclarationSymbol.kt +++ b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkDeclarationSymbol.kt @@ -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 = Pointer { this } override fun getNavigationTargets(project: Project): MutableCollection { return PorkReferenceResolution.getAllProjectPorkFiles(project) - .flatMap { PorkReferenceResolution.findAnyDefinitions(it) } + .flatMap { PorkReferenceResolution.findAnyDefinitions(it.file) } .map { PorkNavigationTarget(it) } .toMutableList() } diff --git a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkElementTypes.kt b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkElementTypes.kt index 98c3e9a..9703188 100644 --- a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkElementTypes.kt +++ b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkElementTypes.kt @@ -35,6 +35,10 @@ object PorkElementTypes { elementTypeFor(TokenType.StringLiteral) ) + val QuoteSet = TokenSet.create( + elementTypeFor(TokenType.Quote) + ) + fun tokenTypeFor(elementType: IElementType): TokenType? = elementTypeToTokenType[elementType] diff --git a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkFile.kt b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkFile.kt index 4098ac7..2961cde 100644 --- a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkFile.kt +++ b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkFile.kt @@ -10,4 +10,8 @@ class PorkFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, PorkL } override fun toString(): String = "Pork" + + override fun isPhysical(): Boolean { + return super.isPhysical() + } } diff --git a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkInlayParameterHintsProvider.kt b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkInlayParameterHintsProvider.kt new file mode 100644 index 0000000..43f4dfb --- /dev/null +++ b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkInlayParameterHintsProvider.kt @@ -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 { + val inlays = mutableListOf() + val resolved = element.reference?.resolve() + if (resolved !is FunctionDefinitionElement) { + return inlays + } + val argumentSpecs = resolved.childrenOfType() + 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 = + mutableSetOf() +} diff --git a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkParameterInfoHandler.kt b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkParameterInfoHandler.kt new file mode 100644 index 0000000..3da7526 --- /dev/null +++ b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkParameterInfoHandler.kt @@ -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 { + override fun findElementForParameterInfo(context: CreateParameterInfoContext): FunctionCallElement? { + return context.file.elementsAtOffsetUp(context.offset).asSequence() + .map { it.first } + .filterIsInstance() + .firstOrNull() + } + + override fun findElementForUpdatingParameterInfo(context: UpdateParameterInfoContext): FunctionCallElement? { + return context.file.elementsAtOffsetUp(context.offset).asSequence() + .map { it.first } + .filterIsInstance() + .firstOrNull() + } + + override fun updateUI(p: FunctionDefinitionElement, context: ParameterInfoUIContext) { + val argumentSpecs = p.childrenOfType() + val signature = argumentSpecs.mapNotNull { it.name }.joinToString(", ") + if (argumentSpecs.isEmpty()) { + context.setupUIComponentPresentation( + "", + -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) + } +} diff --git a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkQuoteHandler.kt b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkQuoteHandler.kt new file mode 100644 index 0000000..1867c81 --- /dev/null +++ b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkQuoteHandler.kt @@ -0,0 +1,5 @@ +package gay.pizza.pork.idea + +import com.intellij.codeInsight.editorActions.SimpleTokenSetQuoteHandler + +class PorkQuoteHandler : SimpleTokenSetQuoteHandler(PorkElementTypes.QuoteSet) diff --git a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkReferenceResolution.kt b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkReferenceResolution.kt deleted file mode 100644 index 8f389a6..0000000 --- a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/PorkReferenceResolution.kt +++ /dev/null @@ -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 { - 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() - val importType = importDeclaration.childrenOfType().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 { - val porkVirtualFiles = FilenameIndex.getAllFilesByExt(project, "pork") - return porkVirtualFiles.mapNotNull { virtualFile -> - PsiManager.getInstance(project).findFile(virtualFile) - } - } - - fun findAllCandidates(internalPorkElement: PorkElement, name: String? = null): List = - listOf(findAnyLocals(internalPorkElement, name), findAnyDefinitions(internalPorkElement.containingFile, name)).flatten() - - fun findAnyLocals(internalPorkElement: PorkElement, name: String? = null): List { - val functionDefinitionElement = PsiTreeUtil.getParentOfType(internalPorkElement, FunctionDefinitionElement::class.java) - ?: return emptyList() - val locals = mutableListOf() - - 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().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() - 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 { - val foundDefinitions = mutableListOf() - for (file in getRelevantFiles(containingFile)) { - val definitions = PsiTreeUtil.collectElements(file) { element -> - element is FunctionDefinitionElement || - element is LetDefinitionElement - }.filterIsInstance() - if (name != null) { - val fileFoundDefinition = definitions.firstOrNull { - it.name == name - } - - if (fileFoundDefinition != null) { - foundDefinitions.add(fileFoundDefinition) - return foundDefinitions - } - } else { - foundDefinitions.addAll(definitions) - } - } - return foundDefinitions - } -} diff --git a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkElementHelpers.kt b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkElementHelpers.kt index 8beb1d9..a1e8832 100644 --- a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkElementHelpers.kt +++ b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkElementHelpers.kt @@ -49,7 +49,13 @@ object PorkElementHelpers { fun referenceOfElement(element: PorkElement, type: NodeType): PsiReference? { unused(type) - val textRangeOfSymbolInElement = element.childrenOfType().firstOrNull()?.textRangeInParent ?: return null + + if (element is ImportPathElement) { + return PorkFileReference(element, element.textRange) + } + + val symbols = element.childrenOfType() + val textRangeOfSymbolInElement = symbols.firstOrNull()?.textRangeInParent ?: return null return PorkIdentifierReference(element, textRangeOfSymbolInElement) } diff --git a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkFileReference.kt b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkFileReference.kt new file mode 100644 index 0000000..8b797df --- /dev/null +++ b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkFileReference.kt @@ -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() ?: return null + val resolved = PorkReferenceResolution.resolveImportFile( + element.containingFile, + PorkReferenceResolution.findPorkStdDirectory(element.project), + importDeclarationElement + ) + return resolved?.file + } + + override fun getVariants(): Array = arrayOf() +} diff --git a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkReferencable.kt b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkReferencable.kt index 48de33a..d001410 100644 --- a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkReferencable.kt +++ b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/PorkReferencable.kt @@ -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 = PorkReferenceResolution.getRelevantFiles(internalPorkElement.containingFile) + fun getRelevantFiles(): List = + PorkReferenceResolution.getRelevantFiles(internalPorkElement.containingFile) + fun findAllCandidates(name: String? = null): List = - listOf(findAnyLocals(name), findAnyDefinitions(name)).flatten() + PorkReferenceResolution.findAllCandidates(internalPorkElement, name) fun findAnyLocals(name: String? = null): List = PorkReferenceResolution.findAnyLocals(internalPorkElement, name) diff --git a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/gen/ImportPathElement.kt b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/gen/ImportPathElement.kt new file mode 100644 index 0000000..34fef39 --- /dev/null +++ b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/gen/ImportPathElement.kt @@ -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) +} diff --git a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/gen/PorkElementFactory.kt b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/gen/PorkElementFactory.kt index 12693cf..f529c74 100644 --- a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/gen/PorkElementFactory.kt +++ b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/psi/gen/PorkElementFactory.kt @@ -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) diff --git a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/resolution/PorkReferenceRelevantFile.kt b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/resolution/PorkReferenceRelevantFile.kt new file mode 100644 index 0000000..f7ea0fa --- /dev/null +++ b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/resolution/PorkReferenceRelevantFile.kt @@ -0,0 +1,5 @@ +package gay.pizza.pork.idea.resolution + +import com.intellij.psi.PsiFile + +class PorkReferenceRelevantFile(val file: PsiFile, val type: PorkRelevantFileType) diff --git a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/resolution/PorkReferenceResolution.kt b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/resolution/PorkReferenceResolution.kt new file mode 100644 index 0000000..d85f139 --- /dev/null +++ b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/resolution/PorkReferenceResolution.kt @@ -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 { + 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().firstOrNull()?.text ?: return null + val importPathElement = importDeclarationElement.childrenOfType().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 { + 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 { + val stdDirectoryPath = findPorkStdDirectory(project) ?: return emptyList() + + val psiManager = PsiManager.getInstance(project) + val stdPorkFiles = mutableListOf() + 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 = + listOf( + findAnyLocals(internalPorkElement, name), + findAnyDefinitions(internalPorkElement.containingFile, name) + ).flatten() + + fun findAnyLocals(internalPorkElement: PorkElement, name: String? = null): List { + val functionDefinitionElement = PsiTreeUtil.getParentOfType( + internalPorkElement, + FunctionDefinitionElement::class.java + ) ?: return emptyList() + val locals = mutableListOf() + + 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().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() + 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 { + val foundDefinitions = mutableListOf() + for (file in getRelevantFiles(containingFile)) { + val definitions = PsiTreeUtil.collectElements(file.file) { element -> + element is FunctionDefinitionElement || + element is LetDefinitionElement + }.filterIsInstance() + if (name != null) { + val fileFoundDefinition = definitions.firstOrNull { + it.name == name + } + + if (fileFoundDefinition != null) { + foundDefinitions.add(fileFoundDefinition) + return foundDefinitions + } + } else { + foundDefinitions.addAll(definitions) + } + } + return foundDefinitions + } +} diff --git a/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/resolution/PorkRelevantFileType.kt b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/resolution/PorkRelevantFileType.kt new file mode 100644 index 0000000..a8f28db --- /dev/null +++ b/support/pork-idea/src/main/kotlin/gay/pizza/pork/idea/resolution/PorkRelevantFileType.kt @@ -0,0 +1,7 @@ +package gay.pizza.pork.idea.resolution + +enum class PorkRelevantFileType { + Self, + Local, + Std +} diff --git a/support/pork-idea/src/main/resources/META-INF/plugin.xml b/support/pork-idea/src/main/resources/META-INF/plugin.xml index 9885899..54a1f29 100644 --- a/support/pork-idea/src/main/resources/META-INF/plugin.xml +++ b/support/pork-idea/src/main/resources/META-INF/plugin.xml @@ -26,6 +26,15 @@ + + +