From f06ea93dc42b16e61c8cf2bb28fc305d4093a426 Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Mon, 4 Sep 2023 18:38:09 -0700 Subject: [PATCH] Significant progress on AST codegen. --- ast/src/main/ast/pork.yml | 123 +++++++++++- .../kotlin/gay/pizza/pork/ast/NodeType.kt | 18 +- .../gay/pizza/pork/gradle/GenerateAstCode.kt | 23 ++- .../gay/pizza/pork/gradle/ast/AstCodegen.kt | 182 ++++++++++++++++++ .../gay/pizza/pork/gradle/ast/AstEnum.kt | 6 + .../pork/gradle/ast/AstEnumDescription.kt | 6 + .../gay/pizza/pork/gradle/ast/AstPrimitive.kt | 3 +- .../gay/pizza/pork/gradle/ast/AstType.kt | 28 +++ .../pork/gradle/ast/AstTypeDescription.kt | 3 +- .../gay/pizza/pork/gradle/ast/AstTypeRef.kt | 13 +- .../pizza/pork/gradle/ast/AstTypeRefForm.kt | 3 +- .../pizza/pork/gradle/ast/AstTypeRegistry.kt | 4 +- .../gay/pizza/pork/gradle/ast/AstTypeRole.kt | 3 +- .../pork/gradle/ast/AstValueDescription.kt | 3 +- .../gay/pizza/pork/gradle/ast/AstWorld.kt | 43 +++-- .../pizza/pork/gradle/ast/RunCodegenIde.kt | 14 ++ .../pizza/pork/gradle/codegen/KotlinClass.kt | 12 ++ .../pork/gradle/codegen/KotlinClassLike.kt | 11 ++ .../pizza/pork/gradle/codegen/KotlinEnum.kt | 12 ++ .../pork/gradle/codegen/KotlinEnumEntry.kt | 6 + .../pork/gradle/codegen/KotlinFunction.kt | 11 ++ .../pizza/pork/gradle/codegen/KotlinMember.kt | 9 + .../pork/gradle/codegen/KotlinParameter.kt | 7 + .../pizza/pork/gradle/codegen/KotlinWriter.kt | 182 ++++++++++++++++++ 24 files changed, 670 insertions(+), 55 deletions(-) create mode 100644 buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstCodegen.kt create mode 100644 buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstEnum.kt create mode 100644 buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstEnumDescription.kt create mode 100644 buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/RunCodegenIde.kt create mode 100644 buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinClass.kt create mode 100644 buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinClassLike.kt create mode 100644 buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinEnum.kt create mode 100644 buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinEnumEntry.kt create mode 100644 buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinFunction.kt create mode 100644 buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinMember.kt create mode 100644 buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinParameter.kt create mode 100644 buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinWriter.kt diff --git a/ast/src/main/ast/pork.yml b/ast/src/main/ast/pork.yml index 3f99d43..0ddd396 100644 --- a/ast/src/main/ast/pork.yml +++ b/ast/src/main/ast/pork.yml @@ -4,6 +4,9 @@ types: parent: Node Symbol: parent: Node + values: + - name: id + type: String Declaration: parent: Node Definition: @@ -11,8 +14,10 @@ types: values: - name: symbol type: Symbol + required: true - name: modifiers type: DefinitionModifiers + required: true DefinitionModifiers: values: - name: export @@ -29,10 +34,126 @@ types: type: List - name: definitions type: List - Assignment: + LetAssignment: + parent: Expression + values: + - name: symbol + type: Symbol + - name: value + type: Expression + InfixOperator: + values: + - name: token + type: String + enums: + - name: Plus + values: + token: "+" + - name: Minus + values: + token: "-" + - name: Multiply + values: + token: "*" + - name: Divide + values: + token: "/" + - name: Equals + values: + token: "==" + - name: NotEquals + values: + token: "!=" + InfixOperation: + parent: Expression + values: + - name: left + type: Expression + - name: op + type: InfixOperator + - name: right + type: Expression + BooleanLiteral: parent: Expression values: + - name: value + type: Boolean + FunctionCall: + parent: Expression + values: + - name: symbol + type: Symbol + - name: arguments + type: List + FunctionDefinition: + parent: Definition + values: + - name: modifiers + type: DefinitionModifiers - name: symbol type: Symbol + - name: arguments + type: List + - name: block + type: Block + If: + parent: Expression + values: + - name: condition + type: Expression + - name: thenExpression + type: Expression + - name: elseExpression + type: Expression? + ImportDeclaration: + parent: Declaration + values: + - name: path + type: StringLiteral + IntLiteral: + parent: Expression + values: - name: value + type: Int + Lambda: + parent: Expression + values: + - name: arguments + type: List + - name: expressions + type: List + ListLiteral: + parent: Expression + values: + - name: items + type: List + Parentheses: + parent: Expression + values: + - name: expression + type: Expression + PrefixOperator: + values: + - name: token + type: String + enums: + - name: Negate + values: + token: "!" + PrefixOperation: + parent: Expression + values: + - name: op + type: PrefixOperator + - name: expression type: Expression + StringLiteral: + parent: Expression + values: + - name: text + type: String + SymbolReference: + parent: Expression + values: + - name: symbol + type: Symbol diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt index d4c0290..ac98843 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt @@ -21,21 +21,5 @@ enum class NodeType(val parent: NodeType? = null) { FunctionCall(Expression), If(Expression), ImportDeclaration(Declaration), - FunctionDefinition(Definition); - - val parents: Set - - init { - val calculatedParents = mutableListOf() - var self = this - while (true) { - calculatedParents.add(self) - if (self.parent != null) { - self = self.parent!! - } else { - break - } - } - parents = calculatedParents.toSet() - } + FunctionDefinition(Definition) } diff --git a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/GenerateAstCode.kt b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/GenerateAstCode.kt index d4c6603..c47ae36 100644 --- a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/GenerateAstCode.kt +++ b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/GenerateAstCode.kt @@ -1,13 +1,11 @@ package gay.pizza.pork.gradle -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory -import com.fasterxml.jackson.module.kotlin.KotlinModule -import gay.pizza.pork.gradle.ast.AstDescription +import gay.pizza.pork.gradle.ast.AstCodegen import org.gradle.api.DefaultTask +import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.TaskAction -import gay.pizza.pork.gradle.ast.AstWorld +import org.gradle.api.tasks.OutputDirectory import java.io.File open class GenerateAstCode : DefaultTask() { @@ -16,15 +14,16 @@ open class GenerateAstCode : DefaultTask() { } @get:InputFile - val astDescriptionFile: File = project.file("src/main/ast/pork.yml") + var astDescriptionFile: File = project.file("src/main/ast/pork.yml") + + @get:Input + var codePackage: String = "gay.pizza.pork.gen" + + @get:OutputDirectory + var outputDirectory: File = project.file("src/main/kotlin/gay/pizza/pork/gen") @TaskAction fun generate() { - val astYamlText = astDescriptionFile.readText() - val mapper = ObjectMapper(YAMLFactory()) - mapper.registerModules(KotlinModule.Builder().build()) - val astDescription = mapper.readValue(astYamlText, AstDescription::class.java) - val world = AstWorld() - world.build(astDescription) + AstCodegen.run(codePackage, astDescriptionFile.toPath(), outputDirectory.toPath()) } } diff --git a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstCodegen.kt b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstCodegen.kt new file mode 100644 index 0000000..6f19a3a --- /dev/null +++ b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstCodegen.kt @@ -0,0 +1,182 @@ +package gay.pizza.pork.gradle.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.gradle.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() + } + } + + fun generate() { + deleteAllContents() + for (type in world.typeRegistry.types) { + writeAstType(type) + } + writeNodeType() + } + + private fun writeNodeType() { + val enumClass = KotlinEnum(pkg, "NodeType") + val parentMember = KotlinMember("parent", "NodeType?", value = "null") + enumClass.members.add(parentMember) + + val typesInNameOrder = world.typeRegistry.types.sortedBy { it.name } + val typesInDependencyOrder = mutableListOf() + for (type in typesInNameOrder) { + if (type.parent != null) { + if (!typesInDependencyOrder.contains(type.parent)) { + typesInDependencyOrder.add(type.parent!!) + } + } + + if (!typesInDependencyOrder.contains(type)) { + typesInDependencyOrder.add(type) + } + } + + for (type in typesInDependencyOrder) { + val role = world.typeRegistry.roleOfType(type) + + if (role == AstTypeRole.ValueHolder || role == AstTypeRole.Enum) { + println(type) + continue + } + + val entry = KotlinEnumEntry(type.name) + if (type.parent != null) { + entry.parameters.add(type.parent!!.name) + } + enumClass.entries.add(entry) + } + + write("NodeType.kt", KotlinWriter(enumClass)) + } + + private fun writeAstType(type: AstType) { + val role = world.typeRegistry.roleOfType(type) + + val kotlinClassLike: KotlinClassLike + if (role == AstTypeRole.Enum) { + val kotlinEnum = KotlinEnum(pkg, type.name) + kotlinClassLike = kotlinEnum + } else { + val kotlinClass = KotlinClass(pkg, type.name) + kotlinClassLike = kotlinClass + if (role == AstTypeRole.RootNode || role == AstTypeRole.HierarchyNode) { + kotlinClass.sealed = true + } + } + + if (role == AstTypeRole.RootNode) { + val typeMember = KotlinMember( + "type", + "NodeType", + abstract = true + ) + kotlinClassLike.members.add(typeMember) + } else if (role == AstTypeRole.AstNode) { + val typeMember = KotlinMember( + "type", + "NodeType", + overridden = true, + value = "NodeType.${type.name}" + ) + kotlinClassLike.members.add(typeMember) + } + + if (type.parent != null) { + val parentName = type.parent!!.name + kotlinClassLike.inherits.add("$parentName()") + } + + for (value in type.values) { + val member = KotlinMember(value.name, toKotlinType(value.typeRef)) + member.abstract = value.abstract + if (type.isParentAbstract(value)) { + member.overridden = true + } + kotlinClassLike.members.add(member) + } + + if (role == AstTypeRole.Enum) { + val kotlinEnum = kotlinClassLike as KotlinEnum + for (entry in type.enums) { + val orderOfKeys = entry.values.keys.sortedBy { key -> + kotlinClassLike.members.indexOfFirst { it.name == key } + } + + val parameters = mutableListOf() + for (key in orderOfKeys) { + val value = entry.values[key] ?: continue + parameters.add("\"${value}\"") + } + val enumEntry = KotlinEnumEntry(entry.name, parameters) + kotlinEnum.entries.add(enumEntry) + } + } + + if (role == AstTypeRole.AstNode) { + val equalsAndHashCodeFields = kotlinClassLike.members.map { it.name } + val equalsFunction = KotlinFunction( + "equals", + returnType = "Boolean", + overridden = true + ) + equalsFunction.parameters.add(KotlinParameter( + "other", + "Any?" + )) + equalsFunction.body.add("if (other !is ${type.name}) return false") + val predicate = equalsAndHashCodeFields.joinToString(" && ") { + "other.${it} == $it" + } + equalsFunction.body.add("return $predicate") + kotlinClassLike.functions.add(equalsFunction) + } + + val serialName = kotlinClassLike.name[0].lowercase() + + kotlinClassLike.name.substring(1) + kotlinClassLike.imports.add("kotlinx.serialization.SerialName") + kotlinClassLike.imports.add("kotlinx.serialization.Serializable") + kotlinClassLike.annotations.add("Serializable") + kotlinClassLike.annotations.add("SerialName(\"$serialName\")") + + write("${type.name}.kt", KotlinWriter(kotlinClassLike)) + } + + private fun toKotlinType(typeRef: AstTypeRef): String { + val baseType = typeRef.type?.name ?: typeRef.primitive?.id + ?: throw RuntimeException("Unable to determine base type.") + return when (typeRef.form) { + AstTypeRefForm.Single -> baseType + AstTypeRefForm.Nullable -> "${baseType}?" + AstTypeRefForm.List -> "List<${baseType}>" + } + } + + private fun write(fileName: String, writer: KotlinWriter) { + val path = outputDirectory.resolve(fileName) + path.deleteIfExists() + path.writeText(writer.toString(), StandardCharsets.UTF_8) + } + + companion object { + fun run(pkg: String, astDescriptionFile: Path, outputDirectory: Path) { + 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/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstEnum.kt b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstEnum.kt new file mode 100644 index 0000000..8aaee9e --- /dev/null +++ b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstEnum.kt @@ -0,0 +1,6 @@ +package gay.pizza.pork.gradle.ast + +class AstEnum( + val name: String, + val values: Map +) diff --git a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstEnumDescription.kt b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstEnumDescription.kt new file mode 100644 index 0000000..eb15f17 --- /dev/null +++ b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstEnumDescription.kt @@ -0,0 +1,6 @@ +package gay.pizza.pork.gradle.ast + +class AstEnumDescription( + val name: String, + val values: Map +) diff --git a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstPrimitive.kt b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstPrimitive.kt index 86c2ebb..baf6965 100644 --- a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstPrimitive.kt +++ b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstPrimitive.kt @@ -2,5 +2,6 @@ package gay.pizza.pork.gradle.ast enum class AstPrimitive(val id: kotlin.String) { Boolean("Boolean"), - String("String") + String("String"), + Int("Int") } diff --git a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstType.kt b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstType.kt index 55c0d22..65cbcea 100644 --- a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstType.kt +++ b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstType.kt @@ -2,11 +2,39 @@ package gay.pizza.pork.gradle.ast class AstType(val name: String, var parent: AstType? = null) { private val internalValues = mutableListOf() + private val internalEnums = mutableListOf() val values: List get() = internalValues + val enums: List + get() = internalEnums + internal fun addValue(value: AstValue) { internalValues.add(value) } + + internal fun addEnum(enum: AstEnum) { + internalEnums.add(enum) + } + + fun isParentAbstract(value: AstValue): Boolean { + if (parent == null) { + return false + } + + var current = parent + while (current != null) { + val abstract = current.values.firstOrNull { + it.name == value.name && it.abstract + } + if (abstract != null) { + return true + } + current = current.parent + } + return false + } + + override fun toString(): String = "AstType(${name})" } diff --git a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstTypeDescription.kt b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstTypeDescription.kt index 1f30f05..92622d6 100644 --- a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstTypeDescription.kt +++ b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstTypeDescription.kt @@ -2,5 +2,6 @@ package gay.pizza.pork.gradle.ast data class AstTypeDescription( val parent: String? = null, - val values: List = emptyList() + val values: List = emptyList(), + val enums: List = emptyList() ) diff --git a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstTypeRef.kt b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstTypeRef.kt index a06f798..5d2439e 100644 --- a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstTypeRef.kt +++ b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstTypeRef.kt @@ -17,15 +17,22 @@ class AstTypeRef( ) } - val primitive = AstPrimitive.values().firstOrNull { it.name == input } + var form = AstTypeRefForm.Single + var typeName: String = input + if (input.endsWith("?")) { + form = AstTypeRefForm.Nullable + typeName = input.substring(0, input.length - 1) + } + + val primitive = AstPrimitive.values().firstOrNull { it.name == typeName } if (primitive != null) { return AstTypeRef( primitive = primitive, - form = AstTypeRefForm.Single + form = form ) } - return AstTypeRef(type = registry.lookup(input), form = AstTypeRefForm.Single) + return AstTypeRef(type = registry.lookup(typeName), form = form) } } } diff --git a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstTypeRefForm.kt b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstTypeRefForm.kt index 8e50f51..685e834 100644 --- a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstTypeRefForm.kt +++ b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstTypeRefForm.kt @@ -2,5 +2,6 @@ package gay.pizza.pork.gradle.ast enum class AstTypeRefForm { Single, - List + List, + Nullable } diff --git a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstTypeRegistry.kt b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstTypeRegistry.kt index 217c985..f776e6a 100644 --- a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstTypeRegistry.kt +++ b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstTypeRegistry.kt @@ -19,7 +19,9 @@ class AstTypeRegistry { fun roleOfType(type: AstType): AstTypeRole = when { - type.parent == null && type.values.isNotEmpty() -> + type.enums.isNotEmpty() -> + AstTypeRole.Enum + type.parent == null && type.values.isEmpty() -> AstTypeRole.RootNode type.parent != null && type.values.all { it.abstract } -> AstTypeRole.HierarchyNode diff --git a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstTypeRole.kt b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstTypeRole.kt index 671dd90..f6cc138 100644 --- a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstTypeRole.kt +++ b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstTypeRole.kt @@ -4,5 +4,6 @@ enum class AstTypeRole { RootNode, HierarchyNode, AstNode, - ValueHolder + ValueHolder, + Enum } diff --git a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstValueDescription.kt b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstValueDescription.kt index 4481a95..dfd69cf 100644 --- a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstValueDescription.kt +++ b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstValueDescription.kt @@ -2,5 +2,6 @@ package gay.pizza.pork.gradle.ast data class AstValueDescription( val name: String, - val type: String + val type: String, + val required: Boolean = false ) diff --git a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstWorld.kt b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstWorld.kt index aad6023..5fb746e 100644 --- a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstWorld.kt +++ b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstWorld.kt @@ -3,26 +3,37 @@ package gay.pizza.pork.gradle.ast class AstWorld { val typeRegistry: AstTypeRegistry = AstTypeRegistry() - fun build(description: AstDescription) { - val rootType = typeRegistry.add(AstType(description.root)) - for (typeName in description.types.keys) { - if (typeName == rootType.name) { - throw RuntimeException("Cannot have type with the same name as the root type.") + companion object { + fun build(description: AstDescription): AstWorld { + val world = AstWorld() + val rootType = world.typeRegistry.add(AstType(description.root)) + for (typeName in description.types.keys) { + if (typeName == rootType.name) { + throw RuntimeException("Cannot have type with the same name as the root type.") + } + + world.typeRegistry.add(AstType(typeName)) } - typeRegistry.add(AstType(typeName)) - } + for ((typeName, typeDescription) in description.types) { + val type = world.typeRegistry.lookup(typeName) - for ((typeName, typeDescription) in description.types) { - val type = typeRegistry.lookup(typeName) - if (typeDescription.parent != null) { - type.parent = typeRegistry.lookup(typeDescription.parent) - } - for (value in typeDescription.values) { - val typeRef = AstTypeRef.parse(value.type, typeRegistry) - val typeValue = AstValue(value.name, typeRef) - type.addValue(typeValue) + if (typeDescription.parent != null) { + type.parent = world.typeRegistry.lookup(typeDescription.parent) + } + + for (value in typeDescription.values) { + val typeRef = AstTypeRef.parse(value.type, world.typeRegistry) + val typeValue = AstValue(value.name, typeRef, abstract = value.required) + type.addValue(typeValue) + } + + for (enum in typeDescription.enums) { + val astEnum = AstEnum(enum.name, enum.values) + type.addEnum(astEnum) + } } + return world } } } diff --git a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/RunCodegenIde.kt b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/RunCodegenIde.kt new file mode 100644 index 0000000..eca1f6d --- /dev/null +++ b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/RunCodegenIde.kt @@ -0,0 +1,14 @@ +package gay.pizza.pork.gradle.ast + +import kotlin.io.path.Path + +object RunCodegenIde { + @JvmStatic + fun main(args: Array) { + AstCodegen.run( + pkg = "gay.pizza.pork.gen", + astDescriptionFile = Path("src/main/ast/pork.yml"), + outputDirectory = Path("src/main/kotlin/gay/pizza/pork/gen") + ) + } +} diff --git a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinClass.kt b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinClass.kt new file mode 100644 index 0000000..ce1f4e1 --- /dev/null +++ b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinClass.kt @@ -0,0 +1,12 @@ +package gay.pizza.pork.gradle.codegen + +class KotlinClass( + override val pkg: String, + override var name: String, + var sealed: Boolean = false, + override var inherits: MutableList = mutableListOf(), + override var imports: MutableList = mutableListOf(), + override var members: MutableList = mutableListOf(), + override var annotations: MutableList = mutableListOf(), + override var functions: MutableList = mutableListOf() +) : KotlinClassLike() diff --git a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinClassLike.kt b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinClassLike.kt new file mode 100644 index 0000000..e88ef87 --- /dev/null +++ b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinClassLike.kt @@ -0,0 +1,11 @@ +package gay.pizza.pork.gradle.codegen + +abstract class KotlinClassLike { + abstract val pkg: String + abstract val name: String + abstract var imports: MutableList + abstract var inherits: MutableList + abstract var annotations: MutableList + abstract var members: MutableList + abstract var functions: MutableList +} diff --git a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinEnum.kt b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinEnum.kt new file mode 100644 index 0000000..a2d48e2 --- /dev/null +++ b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinEnum.kt @@ -0,0 +1,12 @@ +package gay.pizza.pork.gradle.codegen + +class KotlinEnum( + override val pkg: String, + override val name: String, + override var imports: MutableList = mutableListOf(), + override var inherits: MutableList = mutableListOf(), + override var annotations: MutableList = mutableListOf(), + override var members: MutableList = mutableListOf(), + override var functions: MutableList = mutableListOf(), + var entries: MutableList = mutableListOf(), +) : KotlinClassLike() diff --git a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinEnumEntry.kt b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinEnumEntry.kt new file mode 100644 index 0000000..564c3ae --- /dev/null +++ b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinEnumEntry.kt @@ -0,0 +1,6 @@ +package gay.pizza.pork.gradle.codegen + +class KotlinEnumEntry( + val name: String, + var parameters: MutableList = mutableListOf() +) diff --git a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinFunction.kt b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinFunction.kt new file mode 100644 index 0000000..32163ba --- /dev/null +++ b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinFunction.kt @@ -0,0 +1,11 @@ +package gay.pizza.pork.gradle.codegen + +class KotlinFunction( + val name: String, + var parameters: MutableList = mutableListOf(), + var returnType: String? = null, + var abstract: Boolean = false, + var overridden: Boolean = false, + var isImmediateExpression: Boolean = false, + var body: MutableList = mutableListOf() +) diff --git a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinMember.kt b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinMember.kt new file mode 100644 index 0000000..a996647 --- /dev/null +++ b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinMember.kt @@ -0,0 +1,9 @@ +package gay.pizza.pork.gradle.codegen + +class KotlinMember( + var name: String, + var type: String, + var abstract: Boolean = false, + var overridden: Boolean = false, + var value: String? = null +) diff --git a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinParameter.kt b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinParameter.kt new file mode 100644 index 0000000..1118f69 --- /dev/null +++ b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinParameter.kt @@ -0,0 +1,7 @@ +package gay.pizza.pork.gradle.codegen + +class KotlinParameter( + val name: String, + val type: String, + val defaultValue: String? = null +) diff --git a/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinWriter.kt b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinWriter.kt new file mode 100644 index 0000000..c89fca2 --- /dev/null +++ b/buildSrc/src/main/kotlin/gay/pizza/pork/gradle/codegen/KotlinWriter.kt @@ -0,0 +1,182 @@ +package gay.pizza.pork.gradle.codegen + +class KotlinWriter { + private val buffer = StringBuilder() + + constructor(kotlinClassLike: KotlinClassLike) { + write(kotlinClassLike) + } + + fun writeClass(kotlinClass: KotlinClass): Unit = buffer.run { + val classType = if (kotlinClass.sealed) "sealed class" else "class" + writeClassLike(classType, kotlinClass) + val members = kotlinClass.members.filter { + it.abstract || (it.overridden && it.value != null) + } + if (members.isEmpty() && kotlinClass.functions.isEmpty()) { + appendLine() + } else { + appendLine(" {") + } + + for (member in members) { + if (member.abstract) { + appendLine(" abstract val ${member.name}: ${member.type}") + } else { + if (member.overridden) { + append(" override ") + } + append("val ${member.name}: ${member.type}") + if (member.value != null) { + append(" = ") + append(member.value) + } + appendLine() + } + } + + if (members.isNotEmpty() && kotlinClass.functions.isNotEmpty()) { + appendLine() + } + + writeFunctions(kotlinClass) + + if (members.isNotEmpty() || kotlinClass.functions.isNotEmpty()) { + appendLine("}") + } + } + + fun writeEnum(kotlinEnum: KotlinEnum): Unit = buffer.run { + writeClassLike("enum class", kotlinEnum) + val membersNotCompatible = kotlinEnum.members.filter { it.abstract } + if (membersNotCompatible.isNotEmpty()) { + throw RuntimeException( + "Incompatible members in enum class " + + "${kotlinEnum.name}: $membersNotCompatible" + ) + } + + if (kotlinEnum.entries.isEmpty() && kotlinEnum.functions.isEmpty()) { + appendLine() + } else { + appendLine(" {") + } + + for ((index, entry) in kotlinEnum.entries.withIndex()) { + append(" ${entry.name}") + if (entry.parameters.isNotEmpty()) { + append("(") + append(entry.parameters.joinToString(", ")) + append(")") + } + + if (index != kotlinEnum.entries.size - 1) { + append(",") + } + appendLine() + } + + if (kotlinEnum.entries.isNotEmpty() && kotlinEnum.functions.isNotEmpty()) { + appendLine() + } + + writeFunctions(kotlinEnum) + + if (kotlinEnum.entries.isNotEmpty()) { + appendLine("}") + } + } + + private fun writeClassLike( + 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() + } + + for (annotation in kotlinClass.annotations) { + appendLine("@${annotation}") + } + + append("$classType ${kotlinClass.name}") + + val contructedMembers = kotlinClass.members.filter { + !it.abstract && !(it.overridden && it.value != null) + } + + if (contructedMembers.isNotEmpty()) { + val constructor = contructedMembers.joinToString(", ") { + val prefix = if (it.overridden) "override " else "" + val start = "${prefix}val ${it.name}: ${it.type}" + if (it.value != null) { + "$start = ${it.value}" + } else start + } + append("(${constructor})") + } + + if (kotlinClass.inherits.isNotEmpty()) { + append(" : ${kotlinClass.inherits.joinToString(", ")}") + } + } + + private fun writeFunctions(kotlinClassLike: KotlinClassLike): Unit = buffer.run { + for (function in kotlinClassLike.functions) { + append(" ") + + if (function.overridden) { + append("override ") + } + + if (function.abstract) { + append("abstract ") + } + + append("fun ${function.name}(") + append(function.parameters.joinToString(", ") { + val start = "${it.name}: ${it.type}" + if (it.defaultValue != null) { + start + " = ${it.defaultValue}" + } else start + }) + append(")") + if (function.returnType != null) { + append(": ${function.returnType}") + } + + if (!function.isImmediateExpression) { + append(" {") + } else { + appendLine(" =") + } + + if (function.body.isNotEmpty()) { + appendLine() + + for (item in function.body) { + appendLine(" $item") + } + } + + if (!function.isImmediateExpression) { + appendLine(" }") + } + } + } + + fun write(input: KotlinClassLike): Unit = when (input) { + is KotlinClass -> writeClass(input) + is KotlinEnum -> writeEnum(input) + else -> throw RuntimeException("Unknown Kotlin Class Type") + } + + override fun toString(): String = buffer.toString() +}