ast: utilize extension functions to prevent larger stack frames from default interface methods

This commit is contained in:
2023-09-05 14:04:39 -07:00
parent 290d8d0f0a
commit 9f90e05d8a
53 changed files with 491 additions and 282 deletions

View File

@ -4,10 +4,7 @@ import org.gradle.api.JavaVersion
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.withType
import org.gradle.kotlin.dsl.*
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
open class PorkModulePlugin : Plugin<Project> {
@ -16,6 +13,7 @@ open class PorkModulePlugin : Plugin<Project> {
target.apply(plugin = "org.jetbrains.kotlin.plugin.serialization")
target.repositories.mavenCentral()
target.repositories.maven(url = "https://gitlab.com/api/v4/projects/49101454/packages/maven")
target.extensions.getByType<JavaPluginExtension>().apply {
val javaVersion = JavaVersion.toVersion(17)
@ -30,6 +28,8 @@ open class PorkModulePlugin : Plugin<Project> {
target.dependencies {
add("implementation", "org.jetbrains.kotlin:kotlin-bom")
add("implementation", "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
add("api", "gay.pizza.dough:dough-core:0.1.0-SNAPSHOT")
add("api", "gay.pizza.dough:dough-fs:0.1.0-SNAPSHOT")
}
}
}

View File

@ -72,9 +72,32 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
)
visitorInterface.functions.add(visitFunction)
}
write("NodeVisitor.kt", KotlinWriter(visitorInterface))
val visitorExtensionSet = KotlinFunctionSet(pkg)
val visitAnyFunction = KotlinFunction(
"visit",
typeParameters = mutableListOf("T"),
extensionOf = "NodeVisitor<T>",
returnType = "T",
parameters = mutableListOf(
KotlinParameter("node", type = "Node")
),
isImmediateExpression = true
)
visitAnyFunction.body.add("when (node) {")
for (type in world.typeRegistry.types.filter {
world.typeRegistry.roleOfType(it) == AstTypeRole.AstNode
}) {
visitAnyFunction.body.add(" is ${type.name} -> visit${type.name}(node)")
}
visitAnyFunction.body.add("}")
visitorExtensionSet.functions.add(visitAnyFunction)
val visitNodesFunction = KotlinFunction(
"visitNodes",
typeParameters = mutableListOf("T"),
extensionOf = "NodeVisitor<T>",
returnType = "List<T>",
parameters = mutableListOf(
KotlinParameter("nodes", type = "Node?", vararg = true)
@ -82,10 +105,12 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
isImmediateExpression = true
)
visitNodesFunction.body.add("nodes.asSequence().filterNotNull().map { visit(it) }.toList()")
visitorInterface.functions.add(visitNodesFunction)
visitorExtensionSet.functions.add(visitNodesFunction)
val visitAllFunction = KotlinFunction(
"visitAll",
typeParameters = mutableListOf("T"),
extensionOf = "NodeVisitor<T>",
returnType = "List<T>",
parameters = mutableListOf(
KotlinParameter("nodeLists", type = "List<Node>", vararg = true)
@ -93,25 +118,9 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
isImmediateExpression = true
)
visitAllFunction.body.add("nodeLists.asSequence().flatten().map { visit(it) }.toList()")
visitorInterface.functions.add(visitAllFunction)
visitorExtensionSet.functions.add(visitAllFunction)
val visitFunction = KotlinFunction(
"visit",
returnType = "T",
parameters = mutableListOf(
KotlinParameter("node", type = "Node")
),
isImmediateExpression = true
)
visitFunction.body.add("when (node) {")
for (type in world.typeRegistry.types.filter { world.typeRegistry.roleOfType(it) == AstTypeRole.AstNode }) {
visitFunction.body.add(" is ${type.name} -> visit${type.name}(node)")
}
visitFunction.body.add("}")
visitorInterface.functions.add(visitFunction)
write("NodeVisitor.kt", KotlinWriter(visitorInterface))
write("NodeVisitorExtensions.kt", KotlinWriter(visitorExtensionSet))
}
private fun writeNodeCoalescer() {
@ -193,10 +202,21 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
),
isImmediateExpression = true
)
abstractVisitChildrenFunction.body.add("emptyList()")
kotlinClassLike.functions.add(abstractVisitChildrenFunction)
val abstractVisitSelfFunction = KotlinFunction(
"visit",
returnType = "T",
open = true,
typeParameters = mutableListOf("T"),
parameters = mutableListOf(
KotlinParameter("visitor", "NodeVisitor<T>")
),
isImmediateExpression = true
)
abstractVisitSelfFunction.body.add("visitor.visit(this)")
kotlinClassLike.functions.add(abstractVisitSelfFunction)
} else if (role == AstTypeRole.AstNode) {
val typeMember = KotlinMember(
"type",
@ -245,21 +265,21 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
val visitChildrenFunction = KotlinFunction(
"visitChildren",
returnType = "List<T>",
typeParameters = mutableListOf("T")
typeParameters = mutableListOf("T"),
overridden = true,
parameters = mutableListOf(
KotlinParameter("visitor", "NodeVisitor<T>")
),
isImmediateExpression = true
)
visitChildrenFunction.overridden = true
val visitorParameter = KotlinParameter("visitor", "NodeVisitor<T>")
visitChildrenFunction.parameters.add(visitorParameter)
visitChildrenFunction.isImmediateExpression = true
val anyListMembers = type.values.any { it.typeRef.form == AstTypeRefForm.List }
val elideVisitChildren: Boolean
if (anyListMembers) {
val visitParameters = type.values.mapNotNull {
if (it.typeRef.primitive != null) {
null
} else if (it.typeRef.type != null && !world.typeRegistry.roleOfType(it.typeRef.type).isNodeInherited()) {
} else if (it.typeRef.type != null &&
!world.typeRegistry.roleOfType(it.typeRef.type).isNodeInherited()) {
null
} else if (it.typeRef.form == AstTypeRefForm.Single) {
"listOf(${it.name})"
@ -273,7 +293,8 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
val visitParameters = type.values.mapNotNull {
if (it.typeRef.primitive != null) {
null
} else if (it.typeRef.type != null && !world.typeRegistry.roleOfType(it.typeRef.type).isNodeInherited()) {
} else if (it.typeRef.type != null &&
!world.typeRegistry.roleOfType(it.typeRef.type).isNodeInherited()) {
null
} else {
it.name
@ -287,7 +308,22 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
kotlinClassLike.functions.add(visitChildrenFunction)
}
val equalsAndHashCodeMembers = kotlinClassLike.members.map { it.name }.sortedBy { it == "type" }
val visitSelfFunction = KotlinFunction(
"visit",
returnType = "T",
typeParameters = mutableListOf("T"),
overridden = true,
parameters = mutableListOf(
KotlinParameter("visitor", "NodeVisitor<T>")
),
isImmediateExpression = true
)
visitSelfFunction.body.add("visitor.visit${type.name}(this)")
kotlinClassLike.functions.add(visitSelfFunction)
val equalsAndHashCodeMembers = kotlinClassLike.members.map {
it.name
}.sortedBy { it == "type" }
val equalsFunction = KotlinFunction(
"equals",
returnType = "Boolean",
@ -348,9 +384,10 @@ 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(writer.toString(), StandardCharsets.UTF_8)
path.writeText(content, StandardCharsets.UTF_8)
}
companion object {

View File

@ -3,6 +3,7 @@ package gay.pizza.pork.buildext.codegen
class KotlinFunction(
val name: String,
var typeParameters: MutableList<String> = mutableListOf(),
var extensionOf: String? = null,
var parameters: MutableList<KotlinParameter> = mutableListOf(),
var returnType: String? = null,
var abstract: Boolean = false,

View File

@ -0,0 +1,7 @@
package gay.pizza.pork.buildext.codegen
class KotlinFunctionSet(
val pkg: String,
var imports: MutableList<String> = mutableListOf(),
var functions: MutableList<KotlinFunction> = mutableListOf()
)

View File

@ -3,8 +3,8 @@ package gay.pizza.pork.buildext.codegen
class KotlinWriter() {
private val buffer = StringBuilder()
constructor(kotlinClassLike: KotlinClassLike) : this() {
write(kotlinClassLike)
constructor(writable: Any) : this() {
write(writable)
}
fun writeClass(kotlinClass: KotlinClass): Unit = buffer.run {
@ -96,16 +96,7 @@ class KotlinWriter() {
classType: String,
kotlinClass: KotlinClassLike
): Unit = buffer.run {
appendLine("package ${kotlinClass.pkg}")
appendLine()
for (import in kotlinClass.imports) {
appendLine("import $import")
}
if (kotlinClass.imports.isNotEmpty()) {
appendLine()
}
writeHeader(kotlinClass.pkg, kotlinClass.imports)
for (annotation in kotlinClass.annotations) {
appendLine("@${annotation}")
@ -138,76 +129,110 @@ class KotlinWriter() {
}
}
private fun writeFunctions(kotlinClassLike: KotlinClassLike): Unit = buffer.run {
for ((index, function) in kotlinClassLike.functions.withIndex()) {
append(" ")
private fun writeHeader(pkg: String, imports: List<String>): Unit = buffer.run {
appendLine("package $pkg")
appendLine()
if (function.overridden) {
append("override ")
}
for (import in imports) {
appendLine("import $import")
}
if (function.abstract) {
append("abstract ")
}
if (function.open) {
append("open ")
}
append("fun ")
if (function.typeParameters.isNotEmpty()) {
append("<${function.typeParameters.joinToString(", ")}> ")
}
append("${function.name}(")
append(function.parameters.joinToString(", ") {
var start = "${it.name}: ${it.type}"
if (it.vararg) {
start = "vararg $start"
}
if (it.defaultValue != null) {
start + " = ${it.defaultValue}"
} else start
})
append(")")
if (function.returnType != null) {
append(": ${function.returnType}")
}
if (!function.isImmediateExpression && !function.abstract && !function.isInterfaceMethod) {
append(" {")
} else if (!function.abstract && !function.isInterfaceMethod) {
append(" =")
}
if (function.body.isNotEmpty()) {
appendLine()
for (item in function.body) {
appendLine(" $item")
}
}
if (!function.isImmediateExpression && !function.abstract && !function.isInterfaceMethod) {
if (function.body.isNotEmpty()) {
append(" ")
}
appendLine("}")
}
if (function.abstract || function.isInterfaceMethod) {
appendLine()
}
if (index < kotlinClassLike.functions.size - 1) {
appendLine()
}
if (imports.isNotEmpty()) {
appendLine()
}
}
fun write(input: KotlinClassLike): Unit = when (input) {
fun writeFunction(function: KotlinFunction, index: Int = 0, functionCount: Int = 1, indent: String = ""): Unit = buffer.run {
append(indent)
if (function.overridden) {
append("override ")
}
if (function.abstract) {
append("abstract ")
}
if (function.open) {
append("open ")
}
append("fun ")
if (function.typeParameters.isNotEmpty()) {
append("<${function.typeParameters.joinToString(", ")}> ")
}
if (function.extensionOf != null) {
append("${function.extensionOf}.")
}
append("${function.name}(")
append(function.parameters.joinToString(", ") {
var start = "${it.name}: ${it.type}"
if (it.vararg) {
start = "vararg $start"
}
if (it.defaultValue != null) {
start + " = ${it.defaultValue}"
} else start
})
append(")")
if (function.returnType != null) {
append(": ${function.returnType}")
}
if (!function.isImmediateExpression && !function.abstract && !function.isInterfaceMethod) {
append(" {")
} else if (!function.abstract && !function.isInterfaceMethod) {
append(" =")
}
if (function.body.isNotEmpty()) {
appendLine()
for (item in function.body) {
appendLine("$indent $item")
}
}
if (!function.isImmediateExpression && !function.abstract && !function.isInterfaceMethod) {
if (function.body.isNotEmpty()) {
append(indent)
}
appendLine("}")
}
if (function.abstract || function.isInterfaceMethod) {
appendLine()
}
if (index < functionCount - 1) {
appendLine()
}
}
fun writeFunctions(kotlinClassLike: KotlinClassLike): Unit = buffer.run {
for ((index, function) in kotlinClassLike.functions.withIndex()) {
writeFunction(function, index = index, functionCount = kotlinClassLike.functions.size, indent = " ")
}
}
fun writeFunctionSet(functionSet: KotlinFunctionSet) {
writeHeader(functionSet.pkg, functionSet.imports)
for ((index, function) in functionSet.functions.withIndex()) {
writeFunction(
function,
index = index,
functionCount = functionSet.functions.size
)
}
}
fun write(input: Any): Unit = when (input) {
is KotlinClass -> writeClass(input)
is KotlinEnum -> writeEnum(input)
else -> throw RuntimeException("Unknown Kotlin Class Type")
is KotlinFunctionSet -> writeFunctionSet(input)
else -> throw RuntimeException("Unknown Kotlin Type")
}
override fun toString(): String = buffer.toString()