ast: generate graph

This commit is contained in:
2023-09-10 02:34:02 -04:00
parent 0bc3128b9d
commit f433ba2776
7 changed files with 188 additions and 15 deletions

View File

@ -1,12 +1,18 @@
package gay.pizza.pork.buildext
import gay.pizza.pork.buildext.ast.AstCodegen
import gay.pizza.pork.buildext.ast.AstGraph
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.TaskAction
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.OutputFile
import java.io.File
import kotlin.io.path.createDirectories
import kotlin.io.path.deleteIfExists
import kotlin.io.path.writeText
open class GenerateAstCode : DefaultTask() {
init {
@ -22,8 +28,18 @@ open class GenerateAstCode : DefaultTask() {
@get:OutputDirectory
var outputDirectory: File = project.file("src/main/kotlin/gay/pizza/pork/ast")
@get:OutputFile
var typeGraphFile: File = project.file("src/main/graph/types.dot")
@TaskAction
fun generate() {
AstCodegen.run(codePackage, astDescriptionFile.toPath(), outputDirectory.toPath())
val world = AstWorld.read(astDescriptionFile.toPath())
AstCodegen.run(codePackage, world, outputDirectory.toPath())
val typeGraphPath = typeGraphFile.toPath()
typeGraphPath.deleteIfExists()
typeGraphPath.parent.createDirectories()
val graph = AstGraph.from(world)
typeGraphPath.writeText(graph.renderDotGraph())
}
}

View File

@ -1,9 +1,6 @@
@file:Suppress("ConstPropertyName")
package gay.pizza.pork.buildext.ast
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.KotlinModule
import gay.pizza.pork.buildext.codegen.*
import java.nio.charset.StandardCharsets
import java.nio.file.Path
@ -407,15 +404,10 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
companion object {
private const val enableVisitAnyInline = false
fun run(pkg: String, astDescriptionFile: Path, outputDirectory: Path) {
fun run(pkg: String, world: AstWorld, outputDirectory: Path) {
if (!outputDirectory.exists()) {
outputDirectory.createDirectories()
}
val astYamlText = astDescriptionFile.readText()
val mapper = ObjectMapper(YAMLFactory())
mapper.registerModules(KotlinModule.Builder().build())
val astDescription = mapper.readValue(astYamlText, AstDescription::class.java)
val world = AstWorld.build(astDescription)
val codegen = AstCodegen(pkg, outputDirectory, world)
codegen.generate()
}

View File

@ -0,0 +1,58 @@
package gay.pizza.pork.buildext.ast
class AstGraph {
private val nodes = mutableSetOf<AstType>()
private val children = mutableMapOf<AstType, MutableSet<AstType>>()
private val valueReferences = mutableMapOf<AstType, MutableSet<AstType>>()
fun add(type: AstType) {
nodes.add(type)
if (type.parent != null) {
children.getOrPut(type.parent!!) {
mutableSetOf()
}.add(type)
add(type.parent!!)
}
if (type.values != null) {
for (value in type.values!!) {
if (value.typeRef.type != null) {
valueReferences.getOrPut(type) {
mutableSetOf()
}.add(value.typeRef.type)
}
}
}
}
fun renderDotGraph(): String = buildString {
appendLine("digraph A {")
for (node in nodes) {
appendLine(" type_${node.name} [shape=box,label=\"${node.name}\"]")
}
for ((parent, children) in children) {
for (child in children) {
appendLine( " type_${parent.name} -> type_${child.name}")
}
}
for ((type, uses) in valueReferences) {
for (use in uses) {
appendLine(" type_${type.name} -> type_${use.name} [style=dotted]")
}
}
appendLine("}")
}
companion object {
fun from(world: AstWorld): AstGraph {
val graph = AstGraph()
for (type in world.typeRegistry.types) {
graph.add(type)
}
return graph
}
}
}

View File

@ -1,22 +1,38 @@
package gay.pizza.pork.buildext.ast
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.KotlinModule
import java.nio.file.Path
import kotlin.io.path.readText
class AstWorld {
val typeRegistry: AstTypeRegistry = AstTypeRegistry()
fun typesInDependencyOrder(): List<AstType> {
val typesInNameOrder = typeRegistry.types.sortedBy { it.name }
val typesInDependencyOrder = mutableListOf<AstType>()
for (type in typesInNameOrder) {
fun add(type: AstType, resolving: MutableSet<AstType>) {
if (resolving.contains(type)) {
val cyclePath = resolving.joinToString(" -> ") { it.name }
throw RuntimeException("Dependency cycle detected: $cyclePath")
}
resolving.add(type)
if (type.parent != null) {
if (!typesInDependencyOrder.contains(type.parent)) {
typesInDependencyOrder.add(type.parent!!)
}
add(type.parent!!, resolving)
}
if (!typesInDependencyOrder.contains(type)) {
typesInDependencyOrder.add(type)
}
}
for (type in typesInNameOrder) {
add(type, mutableSetOf())
}
return typesInDependencyOrder
}
@ -55,5 +71,13 @@ class AstWorld {
}
return world
}
fun read(path: Path): AstWorld {
val astYamlText = path.readText()
val mapper = ObjectMapper(YAMLFactory())
mapper.registerModules(KotlinModule.Builder().build())
val astDescription = mapper.readValue(astYamlText, AstDescription::class.java)
return build(astDescription)
}
}
}

View File

@ -5,9 +5,10 @@ import kotlin.io.path.Path
object RunCodegenIde {
@JvmStatic
fun main(args: Array<String>) {
val world = AstWorld.read(Path("src/main/ast/pork.yml"))
AstCodegen.run(
pkg = "gay.pizza.pork.ast",
astDescriptionFile = Path("src/main/ast/pork.yml"),
world = world,
outputDirectory = Path("src/main/kotlin/gay/pizza/pork/ast")
)
}