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