Auto-generate the AST.

This commit is contained in:
Alex Zenla 2023-09-04 21:50:27 -07:00
parent f06ea93dc4
commit 174d51ca1c
Signed by: alex
GPG Key ID: C0780728420EBFE5
58 changed files with 741 additions and 390 deletions

View File

@ -15,3 +15,13 @@ jobs:
uses: gradle/gradle-build-action@v2
with:
arguments: build
- name: Archive Pork Bundle
uses: actions/upload-artifact@v3
with:
name: pork-bundle
path: tool/build/distributions/pork.zip
- name: Archive Pork Jar
uses: actions/upload-artifact@v3
with:
name: pork-jar
path: tool/build/distributions/pork-all.jar

22
.github/workflows/graal.yml vendored Normal file
View File

@ -0,0 +1,22 @@
name: graal
on: [push]
jobs:
linux-amd64:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Set up GraalVM
uses: graalvm/setup-graalvm@v1
with:
java-version: '17'
distribution: 'graalvm'
- name: Build with Gradle
uses: gradle/gradle-build-action@v2
with:
arguments: nativeCompile
- name: Archive Pork Executable
uses: actions/upload-artifact@v3
with:
name: pork-linux-amd64
path: tool/build/native/nativeCompile/pork

View File

@ -1,5 +1,4 @@
plugins {
pork_module
id("gay.pizza.pork.module")
id("gay.pizza.pork.ast")
}

View File

@ -33,7 +33,7 @@ types:
- name: declarations
type: List<Declaration>
- name: definitions
type: List<Declaration>
type: List<Definition>
LetAssignment:
parent: Expression
values:

View File

@ -9,15 +9,16 @@ class CompilationUnit(val declarations: List<Declaration>, val definitions: List
override val type: NodeType = NodeType.CompilationUnit
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
visitor.visitAll(declarations)
visitor.visitAll(declarations, definitions)
override fun equals(other: Any?): Boolean {
if (other !is CompilationUnit) return false
return other.declarations == declarations
return other.declarations == declarations && other.definitions == definitions
}
override fun hashCode(): Int {
var result = declarations.hashCode()
result = 31 * result + definitions.hashCode()
result = 31 * result + type.hashCode()
return result
}

View File

@ -1,6 +1,8 @@
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("declaration")
sealed class Declaration : Node()

View File

@ -1,8 +1,10 @@
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("definition")
sealed class Definition : Node() {
abstract val symbol: Symbol
abstract val modifiers: DefinitionModifiers

View File

@ -1,8 +1,8 @@
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
class DefinitionModifiers(
var export: Boolean
)
@SerialName("definitionModifiers")
class DefinitionModifiers(var export: Boolean)

View File

@ -1,6 +1,8 @@
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("expression")
sealed class Expression : Node()

View File

@ -5,24 +5,20 @@ import kotlinx.serialization.Serializable
@Serializable
@SerialName("functionDefinition")
class FunctionDefinition(
override val modifiers: DefinitionModifiers,
override val symbol: Symbol,
val arguments: List<Symbol>,
val block: Block
) : Definition() {
class FunctionDefinition(override val modifiers: DefinitionModifiers, override val symbol: Symbol, val arguments: List<Symbol>, val block: Block) : Definition() {
override val type: NodeType = NodeType.FunctionDefinition
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
visitor.visitNodes(symbol)
visitor.visitAll(listOf(symbol), arguments, listOf(block))
override fun equals(other: Any?): Boolean {
if (other !is FunctionDefinition) return false
return other.symbol == symbol && other.arguments == arguments && other.block == block
return other.modifiers == modifiers && other.symbol == symbol && other.arguments == arguments && other.block == block
}
override fun hashCode(): Int {
var result = symbol.hashCode()
var result = modifiers.hashCode()
result = 31 * result + symbol.hashCode()
result = 31 * result + arguments.hashCode()
result = 31 * result + block.hashCode()
result = 31 * result + type.hashCode()

View File

@ -5,11 +5,7 @@ import kotlinx.serialization.Serializable
@Serializable
@SerialName("if")
class If(
val condition: Expression,
val thenExpression: Expression,
val elseExpression: Expression? = null
) : Expression() {
class If(val condition: Expression, val thenExpression: Expression, val elseExpression: Expression?) : Expression() {
override val type: NodeType = NodeType.If
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
@ -17,15 +13,13 @@ class If(
override fun equals(other: Any?): Boolean {
if (other !is If) return false
return other.condition == condition &&
other.thenExpression == thenExpression &&
other.elseExpression == elseExpression
return other.condition == condition && other.thenExpression == thenExpression && other.elseExpression == elseExpression
}
override fun hashCode(): Int {
var result = condition.hashCode()
result = 31 * result + thenExpression.hashCode()
result = 31 * result + (elseExpression?.hashCode() ?: 0)
result = 31 * result + elseExpression.hashCode()
result = 31 * result + type.hashCode()
return result
}

View File

@ -5,11 +5,7 @@ import kotlinx.serialization.Serializable
@Serializable
@SerialName("infixOperation")
class InfixOperation(
val left: Expression,
val op: InfixOperator,
val right: Expression
) : Expression() {
class InfixOperation(val left: Expression, val op: InfixOperator, val right: Expression) : Expression() {
override val type: NodeType = NodeType.InfixOperation
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
@ -17,9 +13,7 @@ class InfixOperation(
override fun equals(other: Any?): Boolean {
if (other !is InfixOperation) return false
return other.op == op &&
other.left == left &&
other.right == right
return other.left == left && other.op == op && other.right == right
}
override fun hashCode(): Int {

View File

@ -14,7 +14,7 @@ class IntLiteral(val value: Int) : Expression() {
}
override fun hashCode(): Int {
var result = value
var result = value.hashCode()
result = 31 * result + type.hashCode()
return result
}

View File

@ -1,9 +1,13 @@
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("node")
sealed class Node {
abstract val type: NodeType
open fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> = emptyList()
open fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
emptyList()
}

View File

@ -1,26 +1,58 @@
package gay.pizza.pork.ast
class NodeCoalescer(val handler: (Node) -> Unit) : NodeVisitor<Unit> {
override fun visitIntLiteral(node: IntLiteral): Unit = handle(node)
override fun visitStringLiteral(node: StringLiteral): Unit = handle(node)
override fun visitBooleanLiteral(node: BooleanLiteral): Unit = handle(node)
override fun visitListLiteral(node: ListLiteral): Unit = handle(node)
override fun visitSymbol(node: Symbol): Unit = handle(node)
override fun visitFunctionCall(node: FunctionCall): Unit = handle(node)
override fun visitLetAssignment(node: LetAssignment): Unit = handle(node)
override fun visitSymbolReference(node: SymbolReference): Unit = handle(node)
override fun visitLambda(node: Lambda): Unit = handle(node)
override fun visitParentheses(node: Parentheses): Unit = handle(node)
override fun visitPrefixOperation(node: PrefixOperation): Unit = handle(node)
override fun visitIf(node: If): Unit = handle(node)
override fun visitInfixOperation(node: InfixOperation): Unit = handle(node)
override fun visitFunctionDeclaration(node: FunctionDefinition): Unit = handle(node)
override fun visitBlock(node: Block): Unit = handle(node)
override fun visitImportDeclaration(node: ImportDeclaration): Unit = handle(node)
override fun visitBlock(node: Block): Unit =
handle(node)
override fun visitCompilationUnit(node: CompilationUnit): Unit = handle(node)
override fun visitBooleanLiteral(node: BooleanLiteral): Unit =
handle(node)
private fun handle(node: Node) {
override fun visitCompilationUnit(node: CompilationUnit): Unit =
handle(node)
override fun visitFunctionCall(node: FunctionCall): Unit =
handle(node)
override fun visitFunctionDefinition(node: FunctionDefinition): Unit =
handle(node)
override fun visitIf(node: If): Unit =
handle(node)
override fun visitImportDeclaration(node: ImportDeclaration): Unit =
handle(node)
override fun visitInfixOperation(node: InfixOperation): Unit =
handle(node)
override fun visitIntLiteral(node: IntLiteral): Unit =
handle(node)
override fun visitLambda(node: Lambda): Unit =
handle(node)
override fun visitLetAssignment(node: LetAssignment): Unit =
handle(node)
override fun visitListLiteral(node: ListLiteral): Unit =
handle(node)
override fun visitParentheses(node: Parentheses): Unit =
handle(node)
override fun visitPrefixOperation(node: PrefixOperation): Unit =
handle(node)
override fun visitStringLiteral(node: StringLiteral): Unit =
handle(node)
override fun visitSymbol(node: Symbol): Unit =
handle(node)
override fun visitSymbolReference(node: SymbolReference): Unit =
handle(node)
fun handle(node: Node) {
handler(node)
node.visitChildren(this)
}

View File

@ -2,24 +2,24 @@ package gay.pizza.pork.ast
enum class NodeType(val parent: NodeType? = null) {
Node,
Symbol(Node),
Block(Node),
Expression(Node),
BooleanLiteral(Expression),
CompilationUnit(Node),
Declaration(Node),
Definition(Node),
Block(Node),
CompilationUnit(Node),
IntLiteral(Expression),
BooleanLiteral(Expression),
ListLiteral(Expression),
StringLiteral(Expression),
Parentheses(Expression),
LetAssignment(Expression),
Lambda(Expression),
PrefixOperation(Expression),
InfixOperation(Expression),
SymbolReference(Expression),
FunctionCall(Expression),
FunctionDefinition(Definition),
If(Expression),
ImportDeclaration(Declaration),
FunctionDefinition(Definition)
InfixOperation(Expression),
IntLiteral(Expression),
Lambda(Expression),
LetAssignment(Expression),
ListLiteral(Expression),
Parentheses(Expression),
PrefixOperation(Expression),
StringLiteral(Expression),
Symbol(Node),
SymbolReference(Expression)
}

View File

@ -1,60 +1,64 @@
package gay.pizza.pork.ast
interface NodeVisitor<T> {
fun visitIntLiteral(node: IntLiteral): T
fun visitStringLiteral(node: StringLiteral): T
fun visitBooleanLiteral(node: BooleanLiteral): T
fun visitListLiteral(node: ListLiteral): T
fun visitSymbol(node: Symbol): T
fun visitFunctionCall(node: FunctionCall): T
fun visitLetAssignment(node: LetAssignment): T
fun visitSymbolReference(node: SymbolReference): T
fun visitLambda(node: Lambda): T
fun visitParentheses(node: Parentheses): T
fun visitPrefixOperation(node: PrefixOperation): T
fun visitIf(node: If): T
fun visitInfixOperation(node: InfixOperation): T
fun visitFunctionDeclaration(node: FunctionDefinition): T
fun visitBlock(node: Block): T
fun visitImportDeclaration(node: ImportDeclaration): T
fun visitBooleanLiteral(node: BooleanLiteral): T
fun visitCompilationUnit(node: CompilationUnit): T
fun visitExpression(node: Expression): T = when (node) {
is IntLiteral -> visitIntLiteral(node)
is StringLiteral -> visitStringLiteral(node)
is BooleanLiteral -> visitBooleanLiteral(node)
is ListLiteral -> visitListLiteral(node)
is FunctionCall -> visitFunctionCall(node)
is LetAssignment -> visitLetAssignment(node)
is SymbolReference -> visitSymbolReference(node)
is Lambda -> visitLambda(node)
is Parentheses -> visitParentheses(node)
is PrefixOperation -> visitPrefixOperation(node)
is If -> visitIf(node)
is InfixOperation -> visitInfixOperation(node)
}
fun visitFunctionCall(node: FunctionCall): T
fun visitDeclaration(node: Declaration): T = when (node) {
is ImportDeclaration -> visitImportDeclaration(node)
}
fun visitFunctionDefinition(node: FunctionDefinition): T
fun visitDefinition(node: Definition): T = when (node) {
is FunctionDefinition -> visitFunctionDeclaration(node)
}
fun visitIf(node: If): T
fun visit(node: Node): T = when (node) {
is Symbol -> visitSymbol(node)
is Expression -> visitExpression(node)
is CompilationUnit -> visitCompilationUnit(node)
is Block -> visitBlock(node)
is Declaration -> visitDeclaration(node)
is Definition -> visitDefinition(node)
}
fun visitImportDeclaration(node: ImportDeclaration): T
fun visitInfixOperation(node: InfixOperation): T
fun visitIntLiteral(node: IntLiteral): T
fun visitLambda(node: Lambda): T
fun visitLetAssignment(node: LetAssignment): T
fun visitListLiteral(node: ListLiteral): T
fun visitParentheses(node: Parentheses): T
fun visitPrefixOperation(node: PrefixOperation): T
fun visitStringLiteral(node: StringLiteral): T
fun visitSymbol(node: Symbol): T
fun visitSymbolReference(node: SymbolReference): T
fun visitNodes(vararg nodes: Node?): List<T> =
nodes.asSequence().filterNotNull().map { visit(it) }.toList()
fun visitAll(vararg nodeLists: List<Node>): List<T> =
nodeLists.asSequence().flatten().map { visit(it) }.toList()
fun visit(node: Node): T =
when (node) {
is Symbol -> visitSymbol(node)
is Block -> visitBlock(node)
is CompilationUnit -> visitCompilationUnit(node)
is LetAssignment -> visitLetAssignment(node)
is InfixOperation -> visitInfixOperation(node)
is BooleanLiteral -> visitBooleanLiteral(node)
is FunctionCall -> visitFunctionCall(node)
is FunctionDefinition -> visitFunctionDefinition(node)
is If -> visitIf(node)
is ImportDeclaration -> visitImportDeclaration(node)
is IntLiteral -> visitIntLiteral(node)
is Lambda -> visitLambda(node)
is ListLiteral -> visitListLiteral(node)
is Parentheses -> visitParentheses(node)
is PrefixOperation -> visitPrefixOperation(node)
is StringLiteral -> visitStringLiteral(node)
is SymbolReference -> visitSymbolReference(node)
}
}

View File

@ -1,3 +1,8 @@
plugins {
kotlin("jvm") version "1.9.10" apply false
kotlin("plugin.serialization") version "1.9.10" apply false
}
tasks.withType<Wrapper> {
gradleVersion = "8.3"
}

View File

@ -1,182 +0,0 @@
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()
}
}
}

View File

@ -1,9 +0,0 @@
package gay.pizza.pork.gradle.ast
enum class AstTypeRole {
RootNode,
HierarchyNode,
AstNode,
ValueHolder,
Enum
}

View File

@ -1,7 +0,0 @@
package gay.pizza.pork.gradle.codegen
class KotlinParameter(
val name: String,
val type: String,
val defaultValue: String? = null
)

View File

@ -1,25 +0,0 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm")
kotlin("plugin.serialization")
}
repositories {
mavenCentral()
}
java {
val javaVersion = JavaVersion.toVersion(17)
sourceCompatibility = javaVersion
targetCompatibility = javaVersion
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "17"
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-bom")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
}

View File

@ -20,10 +20,18 @@ gradlePlugin {
plugins {
create("pork_ast") {
id = "gay.pizza.pork.ast"
implementationClass = "gay.pizza.pork.gradle.PorkAstPlugin"
implementationClass = "gay.pizza.pork.buildext.PorkAstPlugin"
displayName = "Pork AST"
description = "AST generation code for pork"
}
create("pork_module") {
id = "gay.pizza.pork.module"
implementationClass = "gay.pizza.pork.buildext.PorkModulePlugin"
displayName = "Pork Module"
description = "Module convention for pork"
}
}
}

View File

@ -1,6 +1,6 @@
package gay.pizza.pork.gradle
package gay.pizza.pork.buildext
import gay.pizza.pork.gradle.ast.AstCodegen
import gay.pizza.pork.buildext.ast.AstCodegen
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
@ -17,10 +17,10 @@ open class GenerateAstCode : DefaultTask() {
var astDescriptionFile: File = project.file("src/main/ast/pork.yml")
@get:Input
var codePackage: String = "gay.pizza.pork.gen"
var codePackage: String = "gay.pizza.pork.ast"
@get:OutputDirectory
var outputDirectory: File = project.file("src/main/kotlin/gay/pizza/pork/gen")
var outputDirectory: File = project.file("src/main/kotlin/gay/pizza/pork/ast")
@TaskAction
fun generate() {

View File

@ -1,4 +1,4 @@
package gay.pizza.pork.gradle
package gay.pizza.pork.buildext
import org.gradle.api.Plugin
import org.gradle.api.Project

View File

@ -0,0 +1,35 @@
package gay.pizza.pork.buildext
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.jetbrains.kotlin.gradle.tasks.KotlinCompile
open class PorkModulePlugin : Plugin<Project> {
override fun apply(target: Project) {
target.apply(plugin = "org.jetbrains.kotlin.jvm")
target.apply(plugin = "org.jetbrains.kotlin.plugin.serialization")
target.repositories.mavenCentral()
target.extensions.getByType<JavaPluginExtension>().apply {
val javaVersion = JavaVersion.toVersion(17)
sourceCompatibility = javaVersion
targetCompatibility = javaVersion
}
target.tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "17"
}
target.dependencies {
add("implementation", "org.jetbrains.kotlin:kotlin-bom")
add("implementation", "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
}
}
}

View File

@ -0,0 +1,370 @@
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
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()
writeNodeVisitor()
writeNodeCoalescer()
}
private fun writeNodeType() {
val enumClass = KotlinEnum(pkg, "NodeType")
val parentMember = KotlinMember("parent", "NodeType?", value = "null")
enumClass.members.add(parentMember)
for (type in world.typesInDependencyOrder()) {
val role = world.typeRegistry.roleOfType(type)
if (role == AstTypeRole.ValueHolder || role == AstTypeRole.Enum) {
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 writeNodeVisitor() {
val visitorInterface = KotlinClass(
pkg,
"NodeVisitor",
typeParameters = mutableListOf("T"),
isInterface = true
)
for (type in world.typesInDependencyOrder()) {
val role = world.typeRegistry.roleOfType(type)
if (role != AstTypeRole.AstNode) {
continue
}
val visitFunction = KotlinFunction(
"visit${type.name}",
returnType = "T",
parameters = mutableListOf(
KotlinParameter("node", type.name)
),
isInterfaceMethod = true
)
visitorInterface.functions.add(visitFunction)
}
val visitNodesFunction = KotlinFunction(
"visitNodes",
returnType = "List<T>",
parameters = mutableListOf(
KotlinParameter("nodes", type = "Node?", vararg = true)
),
isImmediateExpression = true
)
visitNodesFunction.body.add("nodes.asSequence().filterNotNull().map { visit(it) }.toList()")
visitorInterface.functions.add(visitNodesFunction)
val visitAllFunction = KotlinFunction(
"visitAll",
returnType = "List<T>",
parameters = mutableListOf(
KotlinParameter("nodeLists", type = "List<Node>", vararg = true)
),
isImmediateExpression = true
)
visitAllFunction.body.add("nodeLists.asSequence().flatten().map { visit(it) }.toList()")
visitorInterface.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))
}
private fun writeNodeCoalescer() {
val coalescerClass = KotlinClass(
pkg,
"NodeCoalescer",
inherits = mutableListOf("NodeVisitor<Unit>"),
members = mutableListOf(
KotlinMember(
"handler",
"(Node) -> Unit"
)
)
)
for (type in world.typesInDependencyOrder()) {
val role = world.typeRegistry.roleOfType(type)
if (role != AstTypeRole.AstNode) {
continue
}
val function = KotlinFunction(
"visit${type.name}",
returnType = "Unit",
parameters = mutableListOf(
KotlinParameter("node", type.name)
),
isImmediateExpression = true,
overridden = true
)
function.body.add("handle(node)")
coalescerClass.functions.add(function)
}
val handleFunction = KotlinFunction(
"handle",
parameters = mutableListOf(
KotlinParameter("node", "Node")
)
)
handleFunction.body.add("handler(node)")
handleFunction.body.add("node.visitChildren(this)")
coalescerClass.functions.add(handleFunction)
write("NodeCoalescer.kt", KotlinWriter(coalescerClass))
}
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)
val abstractVisitChildrenFunction = KotlinFunction(
"visitChildren",
returnType = "List<T>",
open = true,
typeParameters = mutableListOf("T"),
parameters = mutableListOf(
KotlinParameter("visitor", "NodeVisitor<T>")
),
isImmediateExpression = true
)
abstractVisitChildrenFunction.body.add("emptyList()")
kotlinClassLike.functions.add(abstractVisitChildrenFunction)
} 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
}
if (role == AstTypeRole.ValueHolder) {
member.mutable = 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 visitChildrenFunction = KotlinFunction(
"visitChildren",
returnType = "List<T>",
typeParameters = mutableListOf("T")
)
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()) {
null
} else if (it.typeRef.form == AstTypeRefForm.Single) {
"listOf(${it.name})"
} else {
it.name
}
}.joinToString(", ")
elideVisitChildren = visitParameters.isEmpty()
visitChildrenFunction.body.add("visitor.visitAll(${visitParameters})")
} else {
val visitParameters = type.values.mapNotNull {
if (it.typeRef.primitive != null) {
null
} else if (it.typeRef.type != null && !world.typeRegistry.roleOfType(it.typeRef.type).isNodeInherited()) {
null
} else {
it.name
}
}.joinToString(", ")
elideVisitChildren = visitParameters.isEmpty()
visitChildrenFunction.body.add("visitor.visitNodes(${visitParameters})")
}
if (!elideVisitChildren) {
kotlinClassLike.functions.add(visitChildrenFunction)
}
val equalsAndHashCodeMembers = kotlinClassLike.members.map { it.name }.sortedBy { it == "type" }
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 = equalsAndHashCodeMembers.mapNotNull {
if (it == "type") null else "other.${it} == $it"
}.joinToString(" && ")
equalsFunction.body.add("return $predicate")
kotlinClassLike.functions.add(equalsFunction)
val hashCodeFunction = KotlinFunction(
"hashCode",
returnType = "Int",
overridden = true
)
if (equalsAndHashCodeMembers.size == 1) {
val member = equalsAndHashCodeMembers.single()
hashCodeFunction.isImmediateExpression = true
hashCodeFunction.body.add("31 * ${member}.hashCode()")
} else {
for ((index, value) in equalsAndHashCodeMembers.withIndex()) {
if (index == 0) {
hashCodeFunction.body.add("var result = ${value}.hashCode()")
} else {
hashCodeFunction.body.add("result = 31 * result + ${value}.hashCode()")
}
}
hashCodeFunction.body.add("return result")
}
kotlinClassLike.functions.add(hashCodeFunction)
}
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) {
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

@ -1,4 +1,4 @@
package gay.pizza.pork.gradle.ast
package gay.pizza.pork.buildext.ast
data class AstDescription(
val root: String,

View File

@ -1,4 +1,4 @@
package gay.pizza.pork.gradle.ast
package gay.pizza.pork.buildext.ast
class AstEnum(
val name: String,

View File

@ -1,4 +1,4 @@
package gay.pizza.pork.gradle.ast
package gay.pizza.pork.buildext.ast
class AstEnumDescription(
val name: String,

View File

@ -1,4 +1,4 @@
package gay.pizza.pork.gradle.ast
package gay.pizza.pork.buildext.ast
enum class AstPrimitive(val id: kotlin.String) {
Boolean("Boolean"),

View File

@ -1,4 +1,4 @@
package gay.pizza.pork.gradle.ast
package gay.pizza.pork.buildext.ast
class AstType(val name: String, var parent: AstType? = null) {
private val internalValues = mutableListOf<AstValue>()

View File

@ -1,4 +1,4 @@
package gay.pizza.pork.gradle.ast
package gay.pizza.pork.buildext.ast
data class AstTypeDescription(
val parent: String? = null,

View File

@ -1,4 +1,4 @@
package gay.pizza.pork.gradle.ast
package gay.pizza.pork.buildext.ast
class AstTypeRef(
val type: AstType? = null,

View File

@ -1,4 +1,4 @@
package gay.pizza.pork.gradle.ast
package gay.pizza.pork.buildext.ast
enum class AstTypeRefForm {
Single,

View File

@ -1,4 +1,4 @@
package gay.pizza.pork.gradle.ast
package gay.pizza.pork.buildext.ast
class AstTypeRegistry {
private val internalTypes = mutableSetOf<AstType>()

View File

@ -0,0 +1,16 @@
package gay.pizza.pork.buildext.ast
enum class AstTypeRole {
RootNode,
HierarchyNode,
AstNode,
ValueHolder,
Enum;
fun isNodeInherited(): Boolean = when (this) {
RootNode -> true
HierarchyNode -> true
AstNode -> true
else -> false
}
}

View File

@ -1,4 +1,4 @@
package gay.pizza.pork.gradle.ast
package gay.pizza.pork.buildext.ast
class AstValue(
val name: String,

View File

@ -1,4 +1,4 @@
package gay.pizza.pork.gradle.ast
package gay.pizza.pork.buildext.ast
data class AstValueDescription(
val name: String,

View File

@ -1,8 +1,25 @@
package gay.pizza.pork.gradle.ast
package gay.pizza.pork.buildext.ast
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) {
if (type.parent != null) {
if (!typesInDependencyOrder.contains(type.parent)) {
typesInDependencyOrder.add(type.parent!!)
}
}
if (!typesInDependencyOrder.contains(type)) {
typesInDependencyOrder.add(type)
}
}
return typesInDependencyOrder
}
companion object {
fun build(description: AstDescription): AstWorld {
val world = AstWorld()

View File

@ -1,4 +1,4 @@
package gay.pizza.pork.gradle.ast
package gay.pizza.pork.buildext.ast
import kotlin.io.path.Path
@ -6,9 +6,9 @@ object RunCodegenIde {
@JvmStatic
fun main(args: Array<String>) {
AstCodegen.run(
pkg = "gay.pizza.pork.gen",
pkg = "gay.pizza.pork.ast",
astDescriptionFile = Path("src/main/ast/pork.yml"),
outputDirectory = Path("src/main/kotlin/gay/pizza/pork/gen")
outputDirectory = Path("src/main/kotlin/gay/pizza/pork/ast")
)
}
}

View File

@ -1,12 +1,14 @@
package gay.pizza.pork.gradle.codegen
package gay.pizza.pork.buildext.codegen
class KotlinClass(
override val pkg: String,
override var name: String,
var sealed: Boolean = false,
override var inherits: MutableList<String> = mutableListOf(),
var isInterface: Boolean = false,
override var imports: MutableList<String> = mutableListOf(),
override var members: MutableList<KotlinMember> = mutableListOf(),
override var annotations: MutableList<String> = mutableListOf(),
override var typeParameters: MutableList<String> = mutableListOf(),
override var inherits: MutableList<String> = mutableListOf(),
override var members: MutableList<KotlinMember> = mutableListOf(),
override var functions: MutableList<KotlinFunction> = mutableListOf()
) : KotlinClassLike()

View File

@ -1,11 +1,12 @@
package gay.pizza.pork.gradle.codegen
package gay.pizza.pork.buildext.codegen
abstract class KotlinClassLike {
abstract val pkg: String
abstract val name: String
abstract var imports: MutableList<String>
abstract var inherits: MutableList<String>
abstract var annotations: MutableList<String>
abstract var typeParameters: MutableList<String>
abstract var inherits: MutableList<String>
abstract var members: MutableList<KotlinMember>
abstract var functions: MutableList<KotlinFunction>
}

View File

@ -1,12 +1,13 @@
package gay.pizza.pork.gradle.codegen
package gay.pizza.pork.buildext.codegen
class KotlinEnum(
override val pkg: String,
override val name: String,
override var imports: MutableList<String> = mutableListOf(),
override var inherits: MutableList<String> = mutableListOf(),
override var annotations: MutableList<String> = mutableListOf(),
override var typeParameters: MutableList<String> = mutableListOf(),
override var inherits: MutableList<String> = mutableListOf(),
override var members: MutableList<KotlinMember> = mutableListOf(),
override var functions: MutableList<KotlinFunction> = mutableListOf(),
var entries: MutableList<KotlinEnumEntry> = mutableListOf(),
var entries: MutableList<KotlinEnumEntry> = mutableListOf()
) : KotlinClassLike()

View File

@ -1,4 +1,4 @@
package gay.pizza.pork.gradle.codegen
package gay.pizza.pork.buildext.codegen
class KotlinEnumEntry(
val name: String,

View File

@ -1,11 +1,14 @@
package gay.pizza.pork.gradle.codegen
package gay.pizza.pork.buildext.codegen
class KotlinFunction(
val name: String,
var typeParameters: MutableList<String> = mutableListOf(),
var parameters: MutableList<KotlinParameter> = mutableListOf(),
var returnType: String? = null,
var abstract: Boolean = false,
var open: Boolean = false,
var overridden: Boolean = false,
var isImmediateExpression: Boolean = false,
var body: MutableList<String> = mutableListOf()
var body: MutableList<String> = mutableListOf(),
var isInterfaceMethod: Boolean = false
)

View File

@ -1,9 +1,10 @@
package gay.pizza.pork.gradle.codegen
package gay.pizza.pork.buildext.codegen
class KotlinMember(
var name: String,
var type: String,
var abstract: Boolean = false,
var overridden: Boolean = false,
var value: String? = null
var value: String? = null,
var mutable: Boolean = false
)

View File

@ -0,0 +1,8 @@
package gay.pizza.pork.buildext.codegen
class KotlinParameter(
val name: String,
var type: String,
var defaultValue: String? = null,
var vararg: Boolean = false
)

View File

@ -1,14 +1,18 @@
package gay.pizza.pork.gradle.codegen
package gay.pizza.pork.buildext.codegen
class KotlinWriter {
class KotlinWriter() {
private val buffer = StringBuilder()
constructor(kotlinClassLike: KotlinClassLike) {
constructor(kotlinClassLike: KotlinClassLike) : this() {
write(kotlinClassLike)
}
fun writeClass(kotlinClass: KotlinClass): Unit = buffer.run {
val classType = if (kotlinClass.sealed) "sealed class" else "class"
val classType = when {
kotlinClass.sealed -> "sealed class"
kotlinClass.isInterface -> "interface"
else -> "class"
}
writeClassLike(classType, kotlinClass)
val members = kotlinClass.members.filter {
it.abstract || (it.overridden && it.value != null)
@ -20,13 +24,14 @@ class KotlinWriter {
}
for (member in members) {
val form = if (member.mutable) "var" else "val"
if (member.abstract) {
appendLine(" abstract val ${member.name}: ${member.type}")
appendLine(" abstract $form ${member.name}: ${member.type}")
} else {
if (member.overridden) {
append(" override ")
}
append("val ${member.name}: ${member.type}")
append("$form ${member.name}: ${member.type}")
if (member.value != null) {
append(" = ")
append(member.value)
@ -107,6 +112,10 @@ class KotlinWriter {
}
append("$classType ${kotlinClass.name}")
if (kotlinClass.typeParameters.isNotEmpty()) {
val typeParameters = kotlinClass.typeParameters.joinToString(", ")
append("<${typeParameters}>")
}
val contructedMembers = kotlinClass.members.filter {
!it.abstract && !(it.overridden && it.value != null)
@ -115,7 +124,8 @@ class KotlinWriter {
if (contructedMembers.isNotEmpty()) {
val constructor = contructedMembers.joinToString(", ") {
val prefix = if (it.overridden) "override " else ""
val start = "${prefix}val ${it.name}: ${it.type}"
val form = if (it.mutable) "var" else "val"
val start = "${prefix}$form ${it.name}: ${it.type}"
if (it.value != null) {
"$start = ${it.value}"
} else start
@ -129,7 +139,7 @@ class KotlinWriter {
}
private fun writeFunctions(kotlinClassLike: KotlinClassLike): Unit = buffer.run {
for (function in kotlinClassLike.functions) {
for ((index, function) in kotlinClassLike.functions.withIndex()) {
append(" ")
if (function.overridden) {
@ -140,9 +150,20 @@ class KotlinWriter {
append("abstract ")
}
append("fun ${function.name}(")
if (function.open) {
append("open ")
}
append("fun ")
if (function.typeParameters.isNotEmpty()) {
append("<${function.typeParameters.joinToString(", ")}> ")
}
append("${function.name}(")
append(function.parameters.joinToString(", ") {
val start = "${it.name}: ${it.type}"
var start = "${it.name}: ${it.type}"
if (it.vararg) {
start = "vararg $start"
}
if (it.defaultValue != null) {
start + " = ${it.defaultValue}"
} else start
@ -152,10 +173,10 @@ class KotlinWriter {
append(": ${function.returnType}")
}
if (!function.isImmediateExpression) {
if (!function.isImmediateExpression && !function.abstract && !function.isInterfaceMethod) {
append(" {")
} else {
appendLine(" =")
} else if (!function.abstract && !function.isInterfaceMethod) {
append(" =")
}
if (function.body.isNotEmpty()) {
@ -166,9 +187,20 @@ class KotlinWriter {
}
}
if (!function.isImmediateExpression) {
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()
}
}
}

View File

@ -1,3 +1,3 @@
plugins {
pork_module
id("gay.pizza.pork.module")
}

View File

@ -1,5 +1,5 @@
plugins {
pork_module
id("gay.pizza.pork.module")
}
dependencies {

View File

@ -101,7 +101,7 @@ class EvaluationVisitor(root: Scope) : NodeVisitor<Any> {
}
}
override fun visitFunctionDeclaration(node: FunctionDefinition): Any {
override fun visitFunctionDefinition(node: FunctionDefinition): Any {
val blockFunction = visitBlock(node.block) as BlockFunction
val function = CallableFunction { arguments ->
currentScope = currentScope.fork()

View File

@ -1,5 +1,5 @@
plugins {
pork_module
id("gay.pizza.pork.module")
}
dependencies {

1
gradle.properties Normal file
View File

@ -0,0 +1 @@
org.gradle.warning.mode=none

View File

@ -1,5 +1,5 @@
plugins {
pork_module
id("gay.pizza.pork.module")
}
dependencies {

View File

@ -147,7 +147,7 @@ class Printer(buffer: StringBuilder) : NodeVisitor<Unit> {
visit(node.right)
}
override fun visitFunctionDeclaration(node: FunctionDefinition) {
override fun visitFunctionDefinition(node: FunctionDefinition) {
append("fn ")
visit(node.symbol)
append("(")

View File

@ -1,5 +1,7 @@
rootProject.name = "pork"
includeBuild("buildext")
include(
":common",
":ast",

View File

@ -1,6 +1,6 @@
plugins {
application
pork_module
id("gay.pizza.pork.module")
id("com.github.johnrengelman.shadow") version "8.1.1"
id("org.graalvm.buildtools.native") version "0.9.25"
}
@ -15,9 +15,19 @@ dependencies {
}
application {
applicationName = "pork"
mainClass.set("gay.pizza.pork.tool.MainKt")
}
for (task in arrayOf(tasks.shadowDistTar, tasks.shadowDistZip, tasks.shadowJar)) {
val suffix = when {
task == tasks.shadowJar -> ""
task.name.startsWith("shadow") -> "-shadow"
else -> ""
}
task.get().archiveBaseName.set("pork${suffix}")
}
graalvmNative {
binaries {
named("main") {