Skip to content

Commit

Permalink
Significant progress on AST codegen.
Browse files Browse the repository at this point in the history
  • Loading branch information
azenla committed Sep 5, 2023
1 parent edec706 commit f06ea93
Show file tree
Hide file tree
Showing 24 changed files with 670 additions and 55 deletions.
123 changes: 122 additions & 1 deletion ast/src/main/ast/pork.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@ types:
parent: Node
Symbol:
parent: Node
values:
- name: id
type: String
Declaration:
parent: Node
Definition:
parent: Node
values:
- name: symbol
type: Symbol
required: true
- name: modifiers
type: DefinitionModifiers
required: true
DefinitionModifiers:
values:
- name: export
Expand All @@ -29,10 +34,126 @@ types:
type: List<Declaration>
- name: definitions
type: List<Declaration>
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<Expression>
FunctionDefinition:
parent: Definition
values:
- name: modifiers
type: DefinitionModifiers
- name: symbol
type: Symbol
- name: arguments
type: List<Symbol>
- 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<Symbol>
- name: expressions
type: List<Expression>
ListLiteral:
parent: Expression
values:
- name: items
type: List<Expression>
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
18 changes: 1 addition & 17 deletions ast/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,5 @@ enum class NodeType(val parent: NodeType? = null) {
FunctionCall(Expression),
If(Expression),
ImportDeclaration(Declaration),
FunctionDefinition(Definition);

val parents: Set<NodeType>

init {
val calculatedParents = mutableListOf<NodeType>()
var self = this
while (true) {
calculatedParents.add(self)
if (self.parent != null) {
self = self.parent!!
} else {
break
}
}
parents = calculatedParents.toSet()
}
FunctionDefinition(Definition)
}
23 changes: 11 additions & 12 deletions buildSrc/src/main/kotlin/gay/pizza/pork/gradle/GenerateAstCode.kt
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -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())
}
}
182 changes: 182 additions & 0 deletions buildSrc/src/main/kotlin/gay/pizza/pork/gradle/ast/AstCodegen.kt
Original file line number Diff line number Diff line change
@@ -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<AstType>()
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<String>()
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()
}
}
}
Loading

0 comments on commit f06ea93

Please sign in to comment.