diff --git a/.gitignore b/.gitignore index d468ff9..1e412d9 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ out/ /work /kotlin-js-store /.fleet/* +.DS_Store diff --git a/ast/src/main/graph/types.dot b/ast/src/main/graph/types.dot new file mode 100644 index 0000000..68bc44e --- /dev/null +++ b/ast/src/main/graph/types.dot @@ -0,0 +1,81 @@ +digraph A { + type_Node [shape=box,label="Node"] + type_Expression [shape=box,label="Expression"] + type_Symbol [shape=box,label="Symbol"] + type_Declaration [shape=box,label="Declaration"] + type_Definition [shape=box,label="Definition"] + type_DefinitionModifiers [shape=box,label="DefinitionModifiers"] + type_Block [shape=box,label="Block"] + type_CompilationUnit [shape=box,label="CompilationUnit"] + type_LetAssignment [shape=box,label="LetAssignment"] + type_InfixOperator [shape=box,label="InfixOperator"] + type_InfixOperation [shape=box,label="InfixOperation"] + type_BooleanLiteral [shape=box,label="BooleanLiteral"] + type_FunctionCall [shape=box,label="FunctionCall"] + type_FunctionDefinition [shape=box,label="FunctionDefinition"] + type_If [shape=box,label="If"] + type_ImportDeclaration [shape=box,label="ImportDeclaration"] + type_IntegerLiteral [shape=box,label="IntegerLiteral"] + type_DoubleLiteral [shape=box,label="DoubleLiteral"] + type_ListLiteral [shape=box,label="ListLiteral"] + type_Parentheses [shape=box,label="Parentheses"] + type_PrefixOperator [shape=box,label="PrefixOperator"] + type_PrefixOperation [shape=box,label="PrefixOperation"] + type_StringLiteral [shape=box,label="StringLiteral"] + type_SymbolReference [shape=box,label="SymbolReference"] + type_While [shape=box,label="While"] + type_Break [shape=box,label="Break"] + type_Continue [shape=box,label="Continue"] + type_Native [shape=box,label="Native"] + type_Node -> type_Expression + type_Node -> type_Symbol + type_Node -> type_Declaration + type_Node -> type_Definition + type_Node -> type_Block + type_Node -> type_CompilationUnit + type_Node -> type_Native + type_Expression -> type_LetAssignment + type_Expression -> type_InfixOperation + type_Expression -> type_BooleanLiteral + type_Expression -> type_FunctionCall + type_Expression -> type_If + type_Expression -> type_IntegerLiteral + type_Expression -> type_DoubleLiteral + type_Expression -> type_ListLiteral + type_Expression -> type_Parentheses + type_Expression -> type_PrefixOperation + type_Expression -> type_StringLiteral + type_Expression -> type_SymbolReference + type_Expression -> type_While + type_Expression -> type_Break + type_Expression -> type_Continue + type_Definition -> type_FunctionDefinition + type_Declaration -> type_ImportDeclaration + type_Definition -> type_Symbol [style=dotted] + type_Definition -> type_DefinitionModifiers [style=dotted] + type_Block -> type_Expression [style=dotted] + type_CompilationUnit -> type_Declaration [style=dotted] + type_CompilationUnit -> type_Definition [style=dotted] + type_LetAssignment -> type_Symbol [style=dotted] + type_LetAssignment -> type_Expression [style=dotted] + type_InfixOperation -> type_Expression [style=dotted] + type_InfixOperation -> type_InfixOperator [style=dotted] + type_FunctionCall -> type_Symbol [style=dotted] + type_FunctionCall -> type_Expression [style=dotted] + type_FunctionDefinition -> type_DefinitionModifiers [style=dotted] + type_FunctionDefinition -> type_Symbol [style=dotted] + type_FunctionDefinition -> type_Block [style=dotted] + type_FunctionDefinition -> type_Native [style=dotted] + type_If -> type_Expression [style=dotted] + type_If -> type_Block [style=dotted] + type_ImportDeclaration -> type_Symbol [style=dotted] + type_ListLiteral -> type_Expression [style=dotted] + type_Parentheses -> type_Expression [style=dotted] + type_PrefixOperation -> type_PrefixOperator [style=dotted] + type_PrefixOperation -> type_Expression [style=dotted] + type_SymbolReference -> type_Symbol [style=dotted] + type_While -> type_Expression [style=dotted] + type_While -> type_Block [style=dotted] + type_Native -> type_Symbol [style=dotted] + type_Native -> type_StringLiteral [style=dotted] +} diff --git a/buildext/src/main/kotlin/gay/pizza/pork/buildext/GenerateAstCode.kt b/buildext/src/main/kotlin/gay/pizza/pork/buildext/GenerateAstCode.kt index ff7d5bd..e847279 100644 --- a/buildext/src/main/kotlin/gay/pizza/pork/buildext/GenerateAstCode.kt +++ b/buildext/src/main/kotlin/gay/pizza/pork/buildext/GenerateAstCode.kt @@ -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()) } } diff --git a/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstCodegen.kt b/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstCodegen.kt index 81ec30a..5137182 100644 --- a/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstCodegen.kt +++ b/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstCodegen.kt @@ -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() } diff --git a/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstGraph.kt b/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstGraph.kt new file mode 100644 index 0000000..44184b5 --- /dev/null +++ b/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstGraph.kt @@ -0,0 +1,58 @@ +package gay.pizza.pork.buildext.ast + +class AstGraph { + private val nodes = mutableSetOf() + private val children = mutableMapOf>() + private val valueReferences = mutableMapOf>() + + 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 + } + } +} \ No newline at end of file diff --git a/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstWorld.kt b/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstWorld.kt index 6dce257..0fa61b6 100644 --- a/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstWorld.kt +++ b/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstWorld.kt @@ -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 { val typesInNameOrder = typeRegistry.types.sortedBy { it.name } val typesInDependencyOrder = mutableListOf() - for (type in typesInNameOrder) { + + fun add(type: AstType, resolving: MutableSet) { + 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) + } } } diff --git a/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/RunCodegenIde.kt b/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/RunCodegenIde.kt index 9d23be0..3993d2c 100644 --- a/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/RunCodegenIde.kt +++ b/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/RunCodegenIde.kt @@ -5,9 +5,10 @@ import kotlin.io.path.Path object RunCodegenIde { @JvmStatic fun main(args: Array) { + 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") ) }