global: idea generation support

This commit is contained in:
2023-09-17 00:37:58 -07:00
parent 821aa3563a
commit 3b101bd48a
57 changed files with 618 additions and 47 deletions

View File

@ -0,0 +1,6 @@
package gay.pizza.pork.buildext
enum class AstCodegenType {
Standard,
PorkIdea
}

View File

@ -0,0 +1,27 @@
package gay.pizza.pork.buildext
import gay.pizza.pork.buildext.ast.AstPorkIdeaCodegen
import gay.pizza.pork.buildext.ast.AstWorld
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import java.io.File
open class GeneratePorkIdeaAstCode : DefaultTask() {
@get:InputFile
var astDescriptionFile: File = project.project(":ast").file("src/main/ast/pork.yml")
@get:Input
var codePackage: String = "gay.pizza.pork.idea.psi.gen"
@get:OutputDirectory
var outputDirectory: File = project.file("src/main/kotlin/gay/pizza/pork/idea/psi/gen")
@TaskAction
fun generate() {
val world = AstWorld.read(astDescriptionFile.toPath())
AstPorkIdeaCodegen.run(codePackage, world, outputDirectory.toPath())
}
}

View File

@ -1,6 +1,6 @@
package gay.pizza.pork.buildext
import gay.pizza.pork.buildext.ast.AstCodegen
import gay.pizza.pork.buildext.ast.AstStandardCodegen
import gay.pizza.pork.buildext.ast.AstGraph
import gay.pizza.pork.buildext.ast.AstWorld
import org.gradle.api.DefaultTask
@ -14,7 +14,7 @@ import kotlin.io.path.createDirectories
import kotlin.io.path.deleteIfExists
import kotlin.io.path.writeText
open class GenerateAstCode : DefaultTask() {
open class GenerateStandardAstCode : DefaultTask() {
@get:InputFile
var astDescriptionFile: File = project.file("src/main/ast/pork.yml")
@ -30,7 +30,7 @@ open class GenerateAstCode : DefaultTask() {
@TaskAction
fun generate() {
val world = AstWorld.read(astDescriptionFile.toPath())
AstCodegen.run(codePackage, world, outputDirectory.toPath())
AstStandardCodegen.run(codePackage, world, outputDirectory.toPath())
val typeGraphPath = typeGraphFile.toPath()
typeGraphPath.deleteIfExists()

View File

@ -0,0 +1,7 @@
package gay.pizza.pork.buildext
import org.gradle.api.provider.Property
interface PorkAstExtension {
val astCodegenType: Property<AstCodegenType>
}

View File

@ -2,15 +2,32 @@ package gay.pizza.pork.buildext
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.getByType
class PorkAstPlugin : Plugin<Project> {
override fun apply(target: Project) {
val generateAstCode = createGenerateAstCode(target)
val processResources = target.tasks.getByName("processResources")
processResources.dependsOn(generateAstCode)
target.extensions.create("porkAst", PorkAstExtension::class)
target.afterEvaluate {
val generateAstCode = createGenerateAstCode(target)
val processResources = target.tasks.getByName("processResources")
processResources.dependsOn(generateAstCode)
val compileKotlin = target.tasks.getByName("compileKotlin")
compileKotlin.dependsOn(generateAstCode)
}
}
private fun createGenerateAstCode(project: Project): GenerateAstCode =
project.tasks.create("generateAstCode", GenerateAstCode::class)
private fun getAstExtension(project: Project): PorkAstExtension =
project.extensions.getByType<PorkAstExtension>()
private fun createGenerateAstCode(project: Project): Task {
val extension = getAstExtension(project)
val codegenType = extension.astCodegenType.get()
if (codegenType == AstCodegenType.Standard) {
return project.tasks.create("generateAstCode", GenerateStandardAstCode::class)
}
return project.tasks.create("generateAstCode", GeneratePorkIdeaAstCode::class)
}
}

View File

@ -0,0 +1,24 @@
package gay.pizza.pork.buildext.ast
import gay.pizza.pork.buildext.codegen.KotlinWriter
import java.nio.charset.StandardCharsets
import java.nio.file.Path
import kotlin.io.path.deleteExisting
import kotlin.io.path.deleteIfExists
import kotlin.io.path.listDirectoryEntries
import kotlin.io.path.writeText
open class AstCodegenShared(val pkg: String, val outputDirectory: Path, val world: AstWorld) {
protected fun deleteAllContents() {
for (child in outputDirectory.listDirectoryEntries("*.kt")) {
child.deleteExisting()
}
}
protected fun write(fileName: String, writer: KotlinWriter) {
val content = "// GENERATED CODE FROM PORK AST CODEGEN\n$writer"
val path = outputDirectory.resolve(fileName)
path.deleteIfExists()
path.writeText(content, StandardCharsets.UTF_8)
}
}

View File

@ -0,0 +1,164 @@
package gay.pizza.pork.buildext.ast
import gay.pizza.pork.buildext.codegen.KotlinClass
import gay.pizza.pork.buildext.codegen.KotlinFunction
import gay.pizza.pork.buildext.codegen.KotlinParameter
import gay.pizza.pork.buildext.codegen.KotlinWriter
import java.nio.file.Path
import kotlin.io.path.createDirectories
import kotlin.io.path.exists
class AstPorkIdeaCodegen(pkg: String, outputDirectory: Path, world: AstWorld) :
AstCodegenShared(pkg, outputDirectory, world) {
companion object {
fun run(pkg: String, world: AstWorld, outputDirectory: Path) {
if (!outputDirectory.exists()) {
outputDirectory.createDirectories()
}
val codegen = AstPorkIdeaCodegen(pkg, outputDirectory, world)
codegen.generate()
}
}
fun generate() {
deleteAllContents()
writePorkElement()
writeNamedElement()
for (type in world.typeRegistry.types) {
writePsiElement(type)
}
writeElementFactory()
}
fun writePorkElement() {
val baseClass = KotlinClass(
pkg,
"PorkElement",
isAbstract = true,
constructorParameters = mutableMapOf(
"node" to "ASTNode"
),
inherits = mutableListOf(
"ASTWrapperPsiElement(node)"
),
imports = mutableListOf(
"com.intellij.extapi.psi.ASTWrapperPsiElement",
"com.intellij.lang.ASTNode"
)
)
write("PorkElement.kt", KotlinWriter(baseClass))
}
fun writePsiElement(type: AstType) {
val role = world.typeRegistry.roleOfType(type)
if (role != AstTypeRole.AstNode) {
return
}
val baseType =
if (type.namedElementValue != null) "PorkNamedElement" else "PorkElement"
val kotlinClass = KotlinClass(
pkg,
"${type.name}Element",
constructorParameters = mutableMapOf(
"node" to "ASTNode"
),
inherits = mutableListOf("${baseType}(node)"),
imports = mutableListOf(
"com.intellij.lang.ASTNode"
)
)
if (baseType == "PorkNamedElement") {
kotlinClass.imports.add(0, "com.intellij.psi.PsiElement")
kotlinClass.imports.add("gay.pizza.pork.ast.NodeType")
kotlinClass.imports.add("gay.pizza.pork.idea.PorkElementTypes")
val getNameFunction = KotlinFunction(
"getName",
overridden = true,
returnType = "String?",
isImmediateExpression = true
)
getNameFunction.body.add("node.findChildByType(PorkElementTypes.elementTypeFor(NodeType.${type.name}))?.text")
kotlinClass.functions.add(getNameFunction)
val setNameFunction = KotlinFunction(
"setName",
overridden = true,
returnType = "PsiElement",
isImmediateExpression = true,
parameters = mutableListOf(
KotlinParameter("name", "String")
)
)
setNameFunction.body.add("this")
kotlinClass.functions.add(setNameFunction)
}
write("${type.name}Element.kt", KotlinWriter(kotlinClass))
}
fun writeElementFactory() {
val kotlinClass = KotlinClass(
pkg,
"PorkElementFactory",
isObject = true,
imports = mutableListOf(
"com.intellij.extapi.psi.ASTWrapperPsiElement",
"com.intellij.lang.ASTNode",
"com.intellij.psi.PsiElement",
"gay.pizza.pork.ast.NodeType",
"gay.pizza.pork.idea.PorkElementTypes"
)
)
val createFunction = KotlinFunction(
"create",
returnType = "PsiElement",
parameters = mutableListOf(
KotlinParameter("node", "ASTNode")
),
isImmediateExpression = true
)
createFunction.body.add("when (PorkElementTypes.nodeTypeFor(node.elementType)) {")
for (type in world.typeRegistry.types) {
val role = world.typeRegistry.roleOfType(type)
if (role != AstTypeRole.AstNode) {
continue
}
createFunction.body.add(" NodeType.${type.name} -> ${type.name}Element(node)")
}
createFunction.body.add(" else -> ASTWrapperPsiElement(node)")
createFunction.body.add("}")
kotlinClass.functions.add(createFunction)
write("PorkElementFactory.kt", KotlinWriter(kotlinClass))
}
fun writeNamedElement() {
val kotlinClass = KotlinClass(
pkg,
"PorkNamedElement",
isAbstract = true,
constructorParameters = mutableMapOf(
"node" to "ASTNode"
),
inherits = mutableListOf(
"PorkElement(node)",
"PsiNamedElement"
),
imports = mutableListOf(
"com.intellij.lang.ASTNode",
"com.intellij.psi.PsiNamedElement"
)
)
write("PorkNamedElement.kt", KotlinWriter(kotlinClass))
}
}

View File

@ -2,17 +2,12 @@
package gay.pizza.pork.buildext.ast
import gay.pizza.pork.buildext.codegen.*
import java.nio.charset.StandardCharsets
import java.nio.file.Path
import kotlin.io.path.*
class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld) {
private fun deleteAllContents() {
for (child in outputDirectory.listDirectoryEntries("*.kt")) {
child.deleteExisting()
}
}
import kotlin.io.path.createDirectories
import kotlin.io.path.exists
class AstStandardCodegen(pkg: String, outputDirectory: Path, world: AstWorld) :
AstCodegenShared(pkg, outputDirectory, world) {
fun generate() {
deleteAllContents()
for (type in world.typeRegistry.types) {
@ -247,7 +242,7 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
val kotlinClass = KotlinClass(pkg, type.name)
kotlinClassLike = kotlinClass
if (role == AstTypeRole.RootNode || role == AstTypeRole.HierarchyNode) {
kotlinClass.sealed = true
kotlinClass.isSealed = true
}
}
@ -494,13 +489,6 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
}
}
private fun write(fileName: String, writer: KotlinWriter) {
val content = "// GENERATED CODE FROM PORK AST CODEGEN\n$writer"
val path = outputDirectory.resolve(fileName)
path.deleteIfExists()
path.writeText(content, StandardCharsets.UTF_8)
}
companion object {
private const val enableVisitAnyInline = false
@ -508,7 +496,7 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
if (!outputDirectory.exists()) {
outputDirectory.createDirectories()
}
val codegen = AstCodegen(pkg, outputDirectory, world)
val codegen = AstStandardCodegen(pkg, outputDirectory, world)
codegen.generate()
}
}

View File

@ -1,6 +1,6 @@
package gay.pizza.pork.buildext.ast
class AstType(val name: String, var parent: AstType? = null) {
class AstType(val name: String, var parent: AstType? = null, var namedElementValue: String? = null) {
private var internalValues: MutableList<AstValue>? = null
private val internalEnums = mutableListOf<AstEnum>()

View File

@ -3,5 +3,6 @@ package gay.pizza.pork.buildext.ast
data class AstTypeDescription(
val parent: String? = null,
val values: List<AstValueDescription>? = null,
val enums: List<AstEnumDescription> = emptyList()
val enums: List<AstEnumDescription> = emptyList(),
val namedElementValue: String? = null
)

View File

@ -55,6 +55,10 @@ class AstWorld {
type.parent = world.typeRegistry.lookup(typeDescription.parent)
}
if (typeDescription.namedElementValue != null) {
type.namedElementValue = typeDescription.namedElementValue
}
if (typeDescription.values != null) {
type.markHasValues()
for (value in typeDescription.values) {

View File

@ -6,7 +6,7 @@ object RunCodegenIde {
@JvmStatic
fun main(args: Array<String>) {
val world = AstWorld.read(Path("src/main/ast/pork.yml"))
AstCodegen.run(
AstStandardCodegen.run(
pkg = "gay.pizza.pork.ast",
world = world,
outputDirectory = Path("src/main/kotlin/gay/pizza/pork/ast")

View File

@ -3,12 +3,16 @@ package gay.pizza.pork.buildext.codegen
class KotlinClass(
override val pkg: String,
override var name: String,
var sealed: Boolean = false,
var isSealed: Boolean = false,
var isInterface: Boolean = false,
var isOpen: Boolean = false,
var isAbstract: Boolean = false,
var isObject: Boolean = false,
override var imports: MutableList<String> = mutableListOf(),
override var annotations: MutableList<String> = mutableListOf(),
override var typeParameters: MutableList<String> = mutableListOf(),
override var inherits: MutableList<String> = mutableListOf(),
override var members: MutableList<KotlinMember> = mutableListOf(),
override var functions: MutableList<KotlinFunction> = mutableListOf()
override var functions: MutableList<KotlinFunction> = mutableListOf(),
override var constructorParameters: MutableMap<String, String> = mutableMapOf()
) : KotlinClassLike()

View File

@ -9,4 +9,5 @@ abstract class KotlinClassLike {
abstract var inherits: MutableList<String>
abstract var members: MutableList<KotlinMember>
abstract var functions: MutableList<KotlinFunction>
abstract var constructorParameters: MutableMap<String, String>
}

View File

@ -9,5 +9,6 @@ class KotlinEnum(
override var inherits: MutableList<String> = mutableListOf(),
override var members: MutableList<KotlinMember> = mutableListOf(),
override var functions: MutableList<KotlinFunction> = mutableListOf(),
var entries: MutableList<KotlinEnumEntry> = mutableListOf()
override var constructorParameters: MutableMap<String, String> = mutableMapOf(),
var entries: MutableList<KotlinEnumEntry> = mutableListOf(),
) : KotlinClassLike()

View File

@ -8,11 +8,26 @@ class KotlinWriter() {
}
fun writeClass(kotlinClass: KotlinClass): Unit = buffer.run {
val classType = when {
kotlinClass.sealed -> "sealed class"
kotlinClass.isInterface -> "interface"
else -> "class"
val classType = buildString {
if (kotlinClass.isOpen) {
append("open ")
}
if (kotlinClass.isAbstract) {
append("abstract ")
}
if (kotlinClass.isSealed) {
append("sealed ")
}
append(when {
kotlinClass.isInterface -> "interface"
kotlinClass.isObject -> "object"
else -> "class"
})
}
writeClassLike(classType, kotlinClass)
val members = kotlinClass.members.filter {
it.abstract || (it.overridden && it.value != null) || it.notInsideConstructor
@ -136,6 +151,11 @@ class KotlinWriter() {
} else start
}
append("(${constructor})")
} else if (kotlinClass.constructorParameters.isNotEmpty()) {
val constructor = kotlinClass.constructorParameters.entries.joinToString(", ") {
"${it.key}: ${it.value}"
}
append("(${constructor})")
}
if (kotlinClass.inherits.isNotEmpty()) {