ast: utilize extension functions to prevent larger stack frames from default interface methods

This commit is contained in:
Alex Zenla 2023-09-05 14:04:39 -07:00
parent 290d8d0f0a
commit 9f90e05d8a
Signed by: alex
GPG Key ID: C0780728420EBFE5
53 changed files with 491 additions and 282 deletions

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
@ -11,6 +12,9 @@ class Block(val expressions: List<Expression>) : Node() {
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
visitor.visitAll(expressions)
override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitBlock(this)
override fun equals(other: Any?): Boolean {
if (other !is Block) return false
return other.expressions == expressions

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
@ -8,6 +9,9 @@ import kotlinx.serialization.Serializable
class BooleanLiteral(val value: Boolean) : Expression() {
override val type: NodeType = NodeType.BooleanLiteral
override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitBooleanLiteral(this)
override fun equals(other: Any?): Boolean {
if (other !is BooleanLiteral) return false
return other.value == value

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
@ -11,6 +12,9 @@ class CompilationUnit(val declarations: List<Declaration>, val definitions: List
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
visitor.visitAll(declarations, definitions)
override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitCompilationUnit(this)
override fun equals(other: Any?): Boolean {
if (other !is CompilationUnit) return false
return other.declarations == declarations && other.definitions == definitions

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
@ -11,6 +12,9 @@ class FunctionCall(val symbol: Symbol, val arguments: List<Expression>) : Expres
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
visitor.visitAll(listOf(symbol), arguments)
override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitFunctionCall(this)
override fun equals(other: Any?): Boolean {
if (other !is FunctionCall) return false
return other.symbol == symbol && other.arguments == arguments

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
@ -11,6 +12,9 @@ class FunctionDefinition(override val modifiers: DefinitionModifiers, override v
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
visitor.visitAll(listOf(symbol), arguments, listOf(block))
override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitFunctionDefinition(this)
override fun equals(other: Any?): Boolean {
if (other !is FunctionDefinition) return false
return other.modifiers == modifiers && other.symbol == symbol && other.arguments == arguments && other.block == block

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
@ -11,6 +12,9 @@ class If(val condition: Expression, val thenExpression: Expression, val elseExpr
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
visitor.visitNodes(condition, thenExpression, elseExpression)
override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitIf(this)
override fun equals(other: Any?): Boolean {
if (other !is If) return false
return other.condition == condition && other.thenExpression == thenExpression && other.elseExpression == elseExpression

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
@ -11,6 +12,9 @@ class ImportDeclaration(val path: StringLiteral) : Declaration() {
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
visitor.visitNodes(path)
override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitImportDeclaration(this)
override fun equals(other: Any?): Boolean {
if (other !is ImportDeclaration) return false
return other.path == path

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
@ -11,6 +12,9 @@ class InfixOperation(val left: Expression, val op: InfixOperator, val right: Exp
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
visitor.visitNodes(left, right)
override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitInfixOperation(this)
override fun equals(other: Any?): Boolean {
if (other !is InfixOperation) return false
return other.left == left && other.op == op && other.right == right

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
@ -8,6 +9,9 @@ import kotlinx.serialization.Serializable
class IntLiteral(val value: Int) : Expression() {
override val type: NodeType = NodeType.IntLiteral
override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitIntLiteral(this)
override fun equals(other: Any?): Boolean {
if (other !is IntLiteral) return false
return other.value == value

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
@ -11,6 +12,9 @@ class Lambda(val arguments: List<Symbol>, val expressions: List<Expression>) : E
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
visitor.visitAll(arguments, expressions)
override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitLambda(this)
override fun equals(other: Any?): Boolean {
if (other !is Lambda) return false
return other.arguments == arguments && other.expressions == expressions

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
@ -11,6 +12,9 @@ class LetAssignment(val symbol: Symbol, val value: Expression) : Expression() {
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
visitor.visitNodes(symbol, value)
override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitLetAssignment(this)
override fun equals(other: Any?): Boolean {
if (other !is LetAssignment) return false
return other.symbol == symbol && other.value == value

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
@ -11,6 +12,9 @@ class ListLiteral(val items: List<Expression>) : Expression() {
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
visitor.visitAll(items)
override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitListLiteral(this)
override fun equals(other: Any?): Boolean {
if (other !is ListLiteral) return false
return other.items == items

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
@ -10,4 +11,7 @@ sealed class Node {
open fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
emptyList()
open fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visit(this)
}

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
class NodeCoalescer(val handler: (Node) -> Unit) : NodeVisitor<Unit> {

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
enum class NodeType(val parent: NodeType? = null) {

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
interface NodeVisitor<T> {
@ -34,31 +35,4 @@ interface NodeVisitor<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

@ -0,0 +1,29 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
fun <T> NodeVisitor<T>.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)
}
fun <T> NodeVisitor<T>.visitNodes(vararg nodes: Node?): List<T> =
nodes.asSequence().filterNotNull().map { visit(it) }.toList()
fun <T> NodeVisitor<T>.visitAll(vararg nodeLists: List<Node>): List<T> =
nodeLists.asSequence().flatten().map { visit(it) }.toList()

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
@ -11,6 +12,9 @@ class Parentheses(val expression: Expression) : Expression() {
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
visitor.visitNodes(expression)
override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitParentheses(this)
override fun equals(other: Any?): Boolean {
if (other !is Parentheses) return false
return other.expression == expression

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
@ -11,6 +12,9 @@ class PrefixOperation(val op: PrefixOperator, val expression: Expression) : Expr
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
visitor.visitNodes(expression)
override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitPrefixOperation(this)
override fun equals(other: Any?): Boolean {
if (other !is PrefixOperation) return false
return other.op == op && other.expression == expression

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
@ -8,6 +9,9 @@ import kotlinx.serialization.Serializable
class StringLiteral(val text: String) : Expression() {
override val type: NodeType = NodeType.StringLiteral
override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitStringLiteral(this)
override fun equals(other: Any?): Boolean {
if (other !is StringLiteral) return false
return other.text == text

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
@ -8,6 +9,9 @@ import kotlinx.serialization.Serializable
class Symbol(val id: String) : Node() {
override val type: NodeType = NodeType.Symbol
override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitSymbol(this)
override fun equals(other: Any?): Boolean {
if (other !is Symbol) return false
return other.id == id

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
@ -11,6 +12,9 @@ class SymbolReference(val symbol: Symbol) : Expression() {
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
visitor.visitNodes(symbol)
override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitSymbolReference(this)
override fun equals(other: Any?): Boolean {
if (other !is SymbolReference) return false
return other.symbol == symbol

View File

@ -4,10 +4,7 @@ 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.gradle.kotlin.dsl.*
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
open class PorkModulePlugin : Plugin<Project> {
@ -16,6 +13,7 @@ open class PorkModulePlugin : Plugin<Project> {
target.apply(plugin = "org.jetbrains.kotlin.plugin.serialization")
target.repositories.mavenCentral()
target.repositories.maven(url = "https://gitlab.com/api/v4/projects/49101454/packages/maven")
target.extensions.getByType<JavaPluginExtension>().apply {
val javaVersion = JavaVersion.toVersion(17)
@ -30,6 +28,8 @@ open class PorkModulePlugin : Plugin<Project> {
target.dependencies {
add("implementation", "org.jetbrains.kotlin:kotlin-bom")
add("implementation", "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
add("api", "gay.pizza.dough:dough-core:0.1.0-SNAPSHOT")
add("api", "gay.pizza.dough:dough-fs:0.1.0-SNAPSHOT")
}
}
}

View File

@ -72,9 +72,32 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
)
visitorInterface.functions.add(visitFunction)
}
write("NodeVisitor.kt", KotlinWriter(visitorInterface))
val visitorExtensionSet = KotlinFunctionSet(pkg)
val visitAnyFunction = KotlinFunction(
"visit",
typeParameters = mutableListOf("T"),
extensionOf = "NodeVisitor<T>",
returnType = "T",
parameters = mutableListOf(
KotlinParameter("node", type = "Node")
),
isImmediateExpression = true
)
visitAnyFunction.body.add("when (node) {")
for (type in world.typeRegistry.types.filter {
world.typeRegistry.roleOfType(it) == AstTypeRole.AstNode
}) {
visitAnyFunction.body.add(" is ${type.name} -> visit${type.name}(node)")
}
visitAnyFunction.body.add("}")
visitorExtensionSet.functions.add(visitAnyFunction)
val visitNodesFunction = KotlinFunction(
"visitNodes",
typeParameters = mutableListOf("T"),
extensionOf = "NodeVisitor<T>",
returnType = "List<T>",
parameters = mutableListOf(
KotlinParameter("nodes", type = "Node?", vararg = true)
@ -82,10 +105,12 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
isImmediateExpression = true
)
visitNodesFunction.body.add("nodes.asSequence().filterNotNull().map { visit(it) }.toList()")
visitorInterface.functions.add(visitNodesFunction)
visitorExtensionSet.functions.add(visitNodesFunction)
val visitAllFunction = KotlinFunction(
"visitAll",
typeParameters = mutableListOf("T"),
extensionOf = "NodeVisitor<T>",
returnType = "List<T>",
parameters = mutableListOf(
KotlinParameter("nodeLists", type = "List<Node>", vararg = true)
@ -93,25 +118,9 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
isImmediateExpression = true
)
visitAllFunction.body.add("nodeLists.asSequence().flatten().map { visit(it) }.toList()")
visitorInterface.functions.add(visitAllFunction)
visitorExtensionSet.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))
write("NodeVisitorExtensions.kt", KotlinWriter(visitorExtensionSet))
}
private fun writeNodeCoalescer() {
@ -193,10 +202,21 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
),
isImmediateExpression = true
)
abstractVisitChildrenFunction.body.add("emptyList()")
kotlinClassLike.functions.add(abstractVisitChildrenFunction)
val abstractVisitSelfFunction = KotlinFunction(
"visit",
returnType = "T",
open = true,
typeParameters = mutableListOf("T"),
parameters = mutableListOf(
KotlinParameter("visitor", "NodeVisitor<T>")
),
isImmediateExpression = true
)
abstractVisitSelfFunction.body.add("visitor.visit(this)")
kotlinClassLike.functions.add(abstractVisitSelfFunction)
} else if (role == AstTypeRole.AstNode) {
val typeMember = KotlinMember(
"type",
@ -245,21 +265,21 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
val visitChildrenFunction = KotlinFunction(
"visitChildren",
returnType = "List<T>",
typeParameters = mutableListOf("T")
typeParameters = mutableListOf("T"),
overridden = true,
parameters = mutableListOf(
KotlinParameter("visitor", "NodeVisitor<T>")
),
isImmediateExpression = true
)
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()) {
} else if (it.typeRef.type != null &&
!world.typeRegistry.roleOfType(it.typeRef.type).isNodeInherited()) {
null
} else if (it.typeRef.form == AstTypeRefForm.Single) {
"listOf(${it.name})"
@ -273,7 +293,8 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
val visitParameters = type.values.mapNotNull {
if (it.typeRef.primitive != null) {
null
} else if (it.typeRef.type != null && !world.typeRegistry.roleOfType(it.typeRef.type).isNodeInherited()) {
} else if (it.typeRef.type != null &&
!world.typeRegistry.roleOfType(it.typeRef.type).isNodeInherited()) {
null
} else {
it.name
@ -287,7 +308,22 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
kotlinClassLike.functions.add(visitChildrenFunction)
}
val equalsAndHashCodeMembers = kotlinClassLike.members.map { it.name }.sortedBy { it == "type" }
val visitSelfFunction = KotlinFunction(
"visit",
returnType = "T",
typeParameters = mutableListOf("T"),
overridden = true,
parameters = mutableListOf(
KotlinParameter("visitor", "NodeVisitor<T>")
),
isImmediateExpression = true
)
visitSelfFunction.body.add("visitor.visit${type.name}(this)")
kotlinClassLike.functions.add(visitSelfFunction)
val equalsAndHashCodeMembers = kotlinClassLike.members.map {
it.name
}.sortedBy { it == "type" }
val equalsFunction = KotlinFunction(
"equals",
returnType = "Boolean",
@ -348,9 +384,10 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
}
private fun write(fileName: String, writer: KotlinWriter) {
val content = "// GENERATED CODE FROM PORK AST CODEGEN\n$writer"
val path = outputDirectory.resolve(fileName)
path.deleteIfExists()
path.writeText(writer.toString(), StandardCharsets.UTF_8)
path.writeText(content, StandardCharsets.UTF_8)
}
companion object {

View File

@ -3,6 +3,7 @@ package gay.pizza.pork.buildext.codegen
class KotlinFunction(
val name: String,
var typeParameters: MutableList<String> = mutableListOf(),
var extensionOf: String? = null,
var parameters: MutableList<KotlinParameter> = mutableListOf(),
var returnType: String? = null,
var abstract: Boolean = false,

View File

@ -0,0 +1,7 @@
package gay.pizza.pork.buildext.codegen
class KotlinFunctionSet(
val pkg: String,
var imports: MutableList<String> = mutableListOf(),
var functions: MutableList<KotlinFunction> = mutableListOf()
)

View File

@ -3,8 +3,8 @@ package gay.pizza.pork.buildext.codegen
class KotlinWriter() {
private val buffer = StringBuilder()
constructor(kotlinClassLike: KotlinClassLike) : this() {
write(kotlinClassLike)
constructor(writable: Any) : this() {
write(writable)
}
fun writeClass(kotlinClass: KotlinClass): Unit = buffer.run {
@ -96,16 +96,7 @@ class KotlinWriter() {
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()
}
writeHeader(kotlinClass.pkg, kotlinClass.imports)
for (annotation in kotlinClass.annotations) {
appendLine("@${annotation}")
@ -138,76 +129,110 @@ class KotlinWriter() {
}
}
private fun writeFunctions(kotlinClassLike: KotlinClassLike): Unit = buffer.run {
for ((index, function) in kotlinClassLike.functions.withIndex()) {
append(" ")
private fun writeHeader(pkg: String, imports: List<String>): Unit = buffer.run {
appendLine("package $pkg")
appendLine()
if (function.overridden) {
append("override ")
}
for (import in imports) {
appendLine("import $import")
}
if (function.abstract) {
append("abstract ")
}
if (function.open) {
append("open ")
}
append("fun ")
if (function.typeParameters.isNotEmpty()) {
append("<${function.typeParameters.joinToString(", ")}> ")
}
append("${function.name}(")
append(function.parameters.joinToString(", ") {
var start = "${it.name}: ${it.type}"
if (it.vararg) {
start = "vararg $start"
}
if (it.defaultValue != null) {
start + " = ${it.defaultValue}"
} else start
})
append(")")
if (function.returnType != null) {
append(": ${function.returnType}")
}
if (!function.isImmediateExpression && !function.abstract && !function.isInterfaceMethod) {
append(" {")
} else if (!function.abstract && !function.isInterfaceMethod) {
append(" =")
}
if (function.body.isNotEmpty()) {
appendLine()
for (item in function.body) {
appendLine(" $item")
}
}
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()
}
if (imports.isNotEmpty()) {
appendLine()
}
}
fun write(input: KotlinClassLike): Unit = when (input) {
fun writeFunction(function: KotlinFunction, index: Int = 0, functionCount: Int = 1, indent: String = ""): Unit = buffer.run {
append(indent)
if (function.overridden) {
append("override ")
}
if (function.abstract) {
append("abstract ")
}
if (function.open) {
append("open ")
}
append("fun ")
if (function.typeParameters.isNotEmpty()) {
append("<${function.typeParameters.joinToString(", ")}> ")
}
if (function.extensionOf != null) {
append("${function.extensionOf}.")
}
append("${function.name}(")
append(function.parameters.joinToString(", ") {
var start = "${it.name}: ${it.type}"
if (it.vararg) {
start = "vararg $start"
}
if (it.defaultValue != null) {
start + " = ${it.defaultValue}"
} else start
})
append(")")
if (function.returnType != null) {
append(": ${function.returnType}")
}
if (!function.isImmediateExpression && !function.abstract && !function.isInterfaceMethod) {
append(" {")
} else if (!function.abstract && !function.isInterfaceMethod) {
append(" =")
}
if (function.body.isNotEmpty()) {
appendLine()
for (item in function.body) {
appendLine("$indent $item")
}
}
if (!function.isImmediateExpression && !function.abstract && !function.isInterfaceMethod) {
if (function.body.isNotEmpty()) {
append(indent)
}
appendLine("}")
}
if (function.abstract || function.isInterfaceMethod) {
appendLine()
}
if (index < functionCount - 1) {
appendLine()
}
}
fun writeFunctions(kotlinClassLike: KotlinClassLike): Unit = buffer.run {
for ((index, function) in kotlinClassLike.functions.withIndex()) {
writeFunction(function, index = index, functionCount = kotlinClassLike.functions.size, indent = " ")
}
}
fun writeFunctionSet(functionSet: KotlinFunctionSet) {
writeHeader(functionSet.pkg, functionSet.imports)
for ((index, function) in functionSet.functions.withIndex()) {
writeFunction(
function,
index = index,
functionCount = functionSet.functions.size
)
}
}
fun write(input: Any): Unit = when (input) {
is KotlinClass -> writeClass(input)
is KotlinEnum -> writeEnum(input)
else -> throw RuntimeException("Unknown Kotlin Class Type")
is KotlinFunctionSet -> writeFunctionSet(input)
else -> throw RuntimeException("Unknown Kotlin Type")
}
override fun toString(): String = buffer.toString()

View File

@ -0,0 +1,57 @@
package gay.pizza.pork.evaluator
import gay.pizza.pork.ast.CompilationUnit
import gay.pizza.pork.ast.Definition
import gay.pizza.pork.ast.FunctionDefinition
import gay.pizza.pork.ast.ImportDeclaration
class CompilationUnitContext(
val compilationUnit: CompilationUnit,
val evaluator: Evaluator,
rootScope: Scope
) {
val internalScope = rootScope.fork()
val externalScope = rootScope.fork()
private var initialized = false
fun initIfNeeded() {
if (initialized) {
return
}
initialized = true
processAllImports()
processAllDefinitions()
}
private fun processAllDefinitions() {
for (definition in compilationUnit.definitions) {
processDefinition(definition)
}
}
private fun processDefinition(definition: Definition) {
val internalValue = definitionValue(definition)
internalScope.define(definition.symbol.id, internalValue)
if (definition.modifiers.export) {
externalScope.define(definition.symbol.id, internalValue)
}
}
private fun definitionValue(definition: Definition): Any = when (definition) {
is FunctionDefinition -> FunctionContext(definition, internalScope)
}
private fun processAllImports() {
val imports = compilationUnit.declarations.filterIsInstance<ImportDeclaration>()
for (import in imports) {
processImport(import)
}
}
private fun processImport(import: ImportDeclaration) {
val path = import.path.text
val evaluationContext = evaluator.context(path)
internalScope.inherit(evaluationContext.externalScope)
}
}

View File

@ -1,37 +0,0 @@
package gay.pizza.pork.evaluator
import gay.pizza.pork.ast.CompilationUnit
import gay.pizza.pork.ast.ImportDeclaration
class EvaluationContext(
val compilationUnit: CompilationUnit,
val evaluationContextProvider: EvaluationContextProvider,
rootScope: Scope
) {
val internalRootScope = rootScope.fork()
val externalRootScope = rootScope.fork()
private var initialized = false
fun init() {
if (initialized) {
return
}
initialized = true
val imports = compilationUnit.declarations.filterIsInstance<ImportDeclaration>()
for (import in imports) {
val evaluationContext = evaluationContextProvider.provideEvaluationContext(import.path.text)
internalRootScope.inherit(evaluationContext.externalRootScope)
}
for (definition in compilationUnit.definitions) {
val evaluationVisitor = EvaluationVisitor(internalRootScope)
evaluationVisitor.visit(definition)
if (!definition.modifiers.export) {
continue
}
val internalValue = internalRootScope.value(definition.symbol.id)
externalRootScope.define(definition.symbol.id, internalValue)
}
}
}

View File

@ -1,5 +0,0 @@
package gay.pizza.pork.evaluator
interface EvaluationContextProvider {
fun provideEvaluationContext(path: String): EvaluationContext
}

View File

@ -2,23 +2,24 @@ package gay.pizza.pork.evaluator
import gay.pizza.pork.ast.*
class EvaluationVisitor(val root: Scope) : NodeVisitor<Any> {
class EvaluationVisitor(root: Scope) : NodeVisitor<Any> {
private var currentScope: Scope = root
override fun visitIntLiteral(node: IntLiteral): Any = node.value
override fun visitStringLiteral(node: StringLiteral): Any = node.text
override fun visitBooleanLiteral(node: BooleanLiteral): Any = node.value
override fun visitListLiteral(node: ListLiteral): Any = node.items.map { visit(it) }
override fun visitListLiteral(node: ListLiteral): Any =
node.items.map { it.visit(this) }
override fun visitSymbol(node: Symbol): Any = None
override fun visitFunctionCall(node: FunctionCall): Any {
val arguments = node.arguments.map { visit(it) }
val arguments = node.arguments.map { it.visit(this) }
return currentScope.call(node.symbol.id, Arguments(arguments))
}
override fun visitLetAssignment(node: LetAssignment): Any {
val value = visit(node.value)
val value = node.value.visit(this)
currentScope.define(node.symbol.id, value)
return value
}
@ -35,7 +36,7 @@ class EvaluationVisitor(val root: Scope) : NodeVisitor<Any> {
try {
var value: Any? = null
for (expression in node.expressions) {
value = visit(expression)
value = expression.visit(this)
}
value ?: None
} finally {
@ -44,10 +45,11 @@ class EvaluationVisitor(val root: Scope) : NodeVisitor<Any> {
}
}
override fun visitParentheses(node: Parentheses): Any = visit(node.expression)
override fun visitParentheses(node: Parentheses): Any =
node.expression.visit(this)
override fun visitPrefixOperation(node: PrefixOperation): Any {
val value = visit(node.expression)
val value = node.expression.visit(this)
return when (node.op) {
PrefixOperator.Negate -> {
if (value !is Boolean) {
@ -59,21 +61,18 @@ class EvaluationVisitor(val root: Scope) : NodeVisitor<Any> {
}
override fun visitIf(node: If): Any {
val condition = visit(node.condition)
val condition = node.condition.visit(this)
return if (condition == true) {
visit(node.thenExpression)
node.thenExpression.visit(this)
} else {
if (node.elseExpression != null) {
visit(node.elseExpression!!)
} else {
None
}
val elseExpression = node.elseExpression
elseExpression?.visit(this) ?: None
}
}
override fun visitInfixOperation(node: InfixOperation): Any {
val left = visit(node.left)
val right = visit(node.right)
val left = node.left.visit(this)
val right = node.right.visit(this)
when (node.op) {
InfixOperator.Equals -> {
@ -101,28 +100,21 @@ class EvaluationVisitor(val root: Scope) : NodeVisitor<Any> {
}
}
override fun visitFunctionDefinition(node: FunctionDefinition): Any {
val function = CallableFunction { arguments ->
currentScope = root.fork()
for ((index, argumentSymbol) in node.arguments.withIndex()) {
currentScope.define(argumentSymbol.id, arguments.values[index])
}
val visitor = EvaluationVisitor(currentScope)
val blockFunction = visitor.visitBlock(node.block) as BlockFunction
return@CallableFunction blockFunction.call()
}
currentScope.define(node.symbol.id, function)
return None
}
override fun visitBlock(node: Block): Any = BlockFunction {
override fun visitBlock(node: Block): BlockFunction = BlockFunction {
var value: Any? = null
for (expression in node.expressions) {
value = visit(expression)
value = expression.visit(this)
}
value ?: None
}
override fun visitFunctionDefinition(node: FunctionDefinition): Any {
throw RuntimeException(
"Function declarations cannot be visited in an EvaluationVisitor. " +
"Utilize a FunctionContext."
)
}
override fun visitImportDeclaration(node: ImportDeclaration): Any {
throw RuntimeException(
"Import declarations cannot be visited in an EvaluationVisitor. " +

View File

@ -2,21 +2,19 @@ package gay.pizza.pork.evaluator
import gay.pizza.pork.frontend.World
class Evaluator(val world: World, val scope: Scope) : EvaluationContextProvider {
private val contexts = mutableMapOf<String, EvaluationContext>()
class Evaluator(val world: World, val scope: Scope) {
private val contexts = mutableMapOf<String, CompilationUnitContext>()
fun evaluate(path: String): Scope {
val context = provideEvaluationContext(path)
return context.externalRootScope
}
fun evaluate(path: String): Scope =
context(path).externalScope
override fun provideEvaluationContext(path: String): EvaluationContext {
fun context(path: String): CompilationUnitContext {
val unit = world.load(path)
val identity = world.contentSource.stableContentIdentity(path)
val context = contexts.computeIfAbsent(identity) {
EvaluationContext(unit, this, scope)
CompilationUnitContext(unit, this, scope)
}
context.init()
context.initIfNeeded()
return context
}
}

View File

@ -0,0 +1,15 @@
package gay.pizza.pork.evaluator
import gay.pizza.pork.ast.FunctionDefinition
class FunctionContext(val node: FunctionDefinition, val internalScope: Scope) : CallableFunction {
override fun call(arguments: Arguments): Any {
val scope = internalScope.fork()
for ((index, argumentSymbol) in node.arguments.withIndex()) {
scope.define(argumentSymbol.id, arguments.values[index])
}
val visitor = EvaluationVisitor(scope)
val blockFunction = visitor.visitBlock(node.block)
return blockFunction.call()
}
}

View File

@ -14,8 +14,8 @@ class Scope(val parent: Scope? = null, inherits: List<Scope> = emptyList()) {
fun value(name: String): Any {
val value = valueOrNotFound(name)
if (value == NotFound) {
throw RuntimeException("Variable '${name}' not defined.")
if (value === NotFound) {
throw RuntimeException("Variable '${name}' not defined")
}
return value
}
@ -25,14 +25,14 @@ class Scope(val parent: Scope? = null, inherits: List<Scope> = emptyList()) {
if (value == null) {
if (parent != null) {
val parentMaybeFound = parent.valueOrNotFound(name)
if (parentMaybeFound != NotFound) {
if (parentMaybeFound !== NotFound) {
return parentMaybeFound
}
}
for (inherit in inherited) {
val inheritMaybeFound = inherit.valueOrNotFound(name)
if (inheritMaybeFound != NotFound) {
if (inheritMaybeFound !== NotFound) {
return inheritMaybeFound
}
}

View File

@ -1,22 +1,22 @@
package gay.pizza.pork.frontend
import gay.pizza.dough.fs.FsPath
import gay.pizza.dough.fs.PlatformFsProvider
import gay.pizza.dough.fs.readString
import gay.pizza.pork.parser.CharSource
import gay.pizza.pork.parser.StringCharSource
import java.nio.file.Path
import kotlin.io.path.absolutePathString
import kotlin.io.path.readText
class FsContentSource(val root: Path) : ContentSource {
class FsContentSource(val root: FsPath) : ContentSource {
override fun loadAsCharSource(path: String): CharSource =
StringCharSource(asFsPath(path).readText())
StringCharSource(asFsPath(path).readString())
override fun stableContentIdentity(path: String): String =
asFsPath(path).absolutePathString()
asFsPath(path).fullPathString
private fun asFsPath(path: String): Path {
private fun asFsPath(path: String): FsPath {
val fsPath = root.resolve(path)
val absoluteRootPath = root.absolutePathString() + root.fileSystem.separator
if (!fsPath.absolutePathString().startsWith(absoluteRootPath)) {
val rootPathWithSeparator = root.fullPathString + PlatformFsProvider.separator
if (!fsPath.fullPathString.startsWith(rootPathWithSeparator)) {
throw RuntimeException("Unable to load path outside of the root: $fsPath (root is ${root})")
}
return fsPath

View File

@ -2,6 +2,7 @@ package gay.pizza.pork.parser
import gay.pizza.pork.ast.NodeCoalescer
import gay.pizza.pork.ast.Node
import gay.pizza.pork.ast.visit
import java.util.IdentityHashMap
class TokenNodeAttribution : NodeAttribution {

View File

@ -2,14 +2,14 @@ package gay.pizza.pork.tool
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.path
import gay.pizza.dough.fs.PlatformFsProvider
import gay.pizza.pork.ast.Node
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
@OptIn(ExperimentalSerializationApi::class)
class AstCommand : CliktCommand(help = "Print AST", name = "ast") {
val path by argument("file").path(mustExist = true, canBeDir = false)
val path by argument("file")
private val json = Json {
prettyPrint = true
@ -18,7 +18,7 @@ class AstCommand : CliktCommand(help = "Print AST", name = "ast") {
}
override fun run() {
val tool = FileTool(path)
val tool = FileTool(PlatformFsProvider.resolve(path))
println(json.encodeToString(Node.serializer(), tool.parse()))
}
}

View File

@ -2,15 +2,16 @@ package gay.pizza.pork.tool
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.path
import gay.pizza.dough.fs.PlatformFsProvider
import gay.pizza.pork.ast.NodeCoalescer
import gay.pizza.pork.ast.visit
import gay.pizza.pork.parser.TokenNodeAttribution
class AttributeCommand : CliktCommand(help = "Attribute AST", name = "attribute") {
val path by argument("file").path(mustExist = true, canBeDir = false)
val path by argument("file")
override fun run() {
val tool = FileTool(path)
val tool = FileTool(PlatformFsProvider.resolve(path))
val attribution = TokenNodeAttribution()
val compilationUnit = tool.parse(attribution)

View File

@ -1,15 +1,16 @@
package gay.pizza.pork.tool
import gay.pizza.dough.fs.FsPath
import gay.pizza.dough.fs.readString
import gay.pizza.pork.frontend.ContentSource
import gay.pizza.pork.frontend.FsContentSource
import gay.pizza.pork.parser.CharSource
import gay.pizza.pork.parser.StringCharSource
import java.nio.file.Path
import kotlin.io.path.absolute
import kotlin.io.path.readText
class FileTool(val path: Path) : Tool() {
override fun createCharSource(): CharSource = StringCharSource(path.readText())
override fun createContentSource(): ContentSource = FsContentSource(path.absolute().parent)
override fun rootFilePath(): String = path.fileName.toString()
class FileTool(val path: FsPath) : Tool() {
override fun createCharSource(): CharSource =
StringCharSource(path.readString())
override fun createContentSource(): ContentSource =
FsContentSource(path.parent!!)
override fun rootFilePath(): String = path.fullPathString
}

View File

@ -2,14 +2,14 @@ package gay.pizza.pork.tool
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.path
import gay.pizza.dough.fs.PlatformFsProvider
import gay.pizza.pork.parser.AnsiHighlightScheme
class HighlightCommand : CliktCommand(help = "Syntax Highlighter", name = "highlight") {
val path by argument("file").path(mustExist = true, canBeDir = false)
val path by argument("file")
override fun run() {
val tool = FileTool(path)
val tool = FileTool(PlatformFsProvider.resolve(path))
print(tool.highlight(AnsiHighlightScheme()).joinToString(""))
}
}

View File

@ -0,0 +1,23 @@
package gay.pizza.pork.tool
import kotlin.system.measureNanoTime
fun maybeLoopAndMeasure(loop: Boolean, measure: Boolean, block: () -> Unit) {
fun withMaybeMeasurement() {
if (measure) {
val nanos = measureNanoTime(block)
val millis = nanos / 1000000.0
System.err.println("time taken: $millis ms (${nanos} ns)")
} else {
block()
}
}
if (loop) {
while (true) {
withMaybeMeasurement()
}
} else {
withMaybeMeasurement()
}
}

View File

@ -0,0 +1,21 @@
package gay.pizza.pork.tool
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
import gay.pizza.dough.fs.PlatformFsProvider
class ParseCommand : CliktCommand(help = "Parse Compilation Unit", name = "parse") {
val loop by option("--loop", help = "Loop Parsing").flag()
val measure by option("--measure", help = "Measure Time").flag()
val path by argument("file")
override fun run() {
val tool = FileTool(PlatformFsProvider.resolve(path))
maybeLoopAndMeasure(loop, measure) {
tool.parse()
}
}
}

View File

@ -2,13 +2,13 @@ package gay.pizza.pork.tool
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.path
import gay.pizza.dough.fs.PlatformFsProvider
class ReprintCommand : CliktCommand(help = "Reprint Parsed Compilation Unit", name = "reprint") {
val path by argument("file").path(mustExist = true, canBeDir = false)
val path by argument("file")
override fun run() {
val tool = FileTool(path)
val tool = FileTool(PlatformFsProvider.resolve(path))
print(tool.reprint())
}
}

View File

@ -13,6 +13,7 @@ class RootCommand : CliktCommand(
HighlightCommand(),
TokenizeCommand(),
ReprintCommand(),
ParseCommand(),
AstCommand(),
AttributeCommand()
)

View File

@ -4,45 +4,32 @@ import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.types.path
import gay.pizza.dough.fs.PlatformFsProvider
import gay.pizza.pork.evaluator.CallableFunction
import gay.pizza.pork.evaluator.None
import gay.pizza.pork.evaluator.Scope
import kotlin.system.measureTimeMillis
class RunCommand : CliktCommand(help = "Run Program", name = "run") {
val loop by option("--loop", help = "Loop Program").flag()
val measure by option("--measure", help = "Measure Time").flag()
val path by argument("file").path(mustExist = true, canBeDir = false)
val quiet by option("--quiet", help = "Silence Prints").flag()
val path by argument("file")
override fun run() {
if (loop) {
while (true) {
runProgramMaybeMeasure()
}
} else {
runProgramMaybeMeasure()
}
}
private fun runProgramMaybeMeasure() {
if (measure) {
val time = measureTimeMillis {
runProgramOnce()
}
println("time taken: $time ms")
} else {
runProgramOnce()
}
}
private fun runProgramOnce() {
val tool = FileTool(path)
val tool = FileTool(PlatformFsProvider.resolve(path))
val scope = Scope()
scope.define("println", CallableFunction { arguments ->
if (quiet) {
return@CallableFunction None
}
for (argument in arguments.values) {
println(argument)
}
None
})
tool.evaluate(scope)
maybeLoopAndMeasure(loop, measure) {
tool.evaluate(scope)
}
}
}

View File

@ -2,13 +2,13 @@ package gay.pizza.pork.tool
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.path
import gay.pizza.dough.fs.PlatformFsProvider
class TokenizeCommand : CliktCommand(help = "Tokenize Compilation Unit", name = "tokenize") {
val path by argument("file").path(mustExist = true, canBeDir = false)
val path by argument("file")
override fun run() {
val tool = FileTool(path)
val tool = FileTool(PlatformFsProvider.resolve(path))
val tokenStream = tool.tokenize()
for (token in tokenStream.tokens) {
println("${token.start} ${token.type.name} '${sanitize(token.text)}'")

View File

@ -3,6 +3,7 @@ package gay.pizza.pork.tool
import gay.pizza.pork.ast.NodeVisitor
import gay.pizza.pork.parser.Printer
import gay.pizza.pork.ast.CompilationUnit
import gay.pizza.pork.ast.visit
import gay.pizza.pork.evaluator.Arguments
import gay.pizza.pork.evaluator.Evaluator
import gay.pizza.pork.evaluator.Scope