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 package gay.pizza.pork.ast
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
@ -11,6 +12,9 @@ class Block(val expressions: List<Expression>) : Node() {
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> = override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
visitor.visitAll(expressions) visitor.visitAll(expressions)
override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitBlock(this)
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (other !is Block) return false if (other !is Block) return false
return other.expressions == expressions return other.expressions == expressions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast package gay.pizza.pork.ast
import kotlinx.serialization.SerialName 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> = override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
visitor.visitAll(listOf(symbol), arguments, listOf(block)) visitor.visitAll(listOf(symbol), arguments, listOf(block))
override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitFunctionDefinition(this)
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (other !is FunctionDefinition) return false if (other !is FunctionDefinition) return false
return other.modifiers == modifiers && other.symbol == symbol && other.arguments == arguments && other.block == block 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 package gay.pizza.pork.ast
import kotlinx.serialization.SerialName 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> = override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
visitor.visitNodes(condition, thenExpression, elseExpression) visitor.visitNodes(condition, thenExpression, elseExpression)
override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitIf(this)
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (other !is If) return false 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,4 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast package gay.pizza.pork.ast
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
@ -10,4 +11,7 @@ sealed class Node {
open fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> = open fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
emptyList() 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 package gay.pizza.pork.ast
class NodeCoalescer(val handler: (Node) -> Unit) : NodeVisitor<Unit> { 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 package gay.pizza.pork.ast
enum class NodeType(val parent: NodeType? = null) { enum class NodeType(val parent: NodeType? = null) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,10 +4,7 @@ import org.gradle.api.JavaVersion
import org.gradle.api.Plugin import org.gradle.api.Plugin
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.plugins.JavaPluginExtension import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.*
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 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
open class PorkModulePlugin : Plugin<Project> { open class PorkModulePlugin : Plugin<Project> {
@ -16,6 +13,7 @@ open class PorkModulePlugin : Plugin<Project> {
target.apply(plugin = "org.jetbrains.kotlin.plugin.serialization") target.apply(plugin = "org.jetbrains.kotlin.plugin.serialization")
target.repositories.mavenCentral() target.repositories.mavenCentral()
target.repositories.maven(url = "https://gitlab.com/api/v4/projects/49101454/packages/maven")
target.extensions.getByType<JavaPluginExtension>().apply { target.extensions.getByType<JavaPluginExtension>().apply {
val javaVersion = JavaVersion.toVersion(17) val javaVersion = JavaVersion.toVersion(17)
@ -30,6 +28,8 @@ open class PorkModulePlugin : Plugin<Project> {
target.dependencies { target.dependencies {
add("implementation", "org.jetbrains.kotlin:kotlin-bom") add("implementation", "org.jetbrains.kotlin:kotlin-bom")
add("implementation", "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0") 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) 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( val visitNodesFunction = KotlinFunction(
"visitNodes", "visitNodes",
typeParameters = mutableListOf("T"),
extensionOf = "NodeVisitor<T>",
returnType = "List<T>", returnType = "List<T>",
parameters = mutableListOf( parameters = mutableListOf(
KotlinParameter("nodes", type = "Node?", vararg = true) KotlinParameter("nodes", type = "Node?", vararg = true)
@ -82,10 +105,12 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
isImmediateExpression = true isImmediateExpression = true
) )
visitNodesFunction.body.add("nodes.asSequence().filterNotNull().map { visit(it) }.toList()") visitNodesFunction.body.add("nodes.asSequence().filterNotNull().map { visit(it) }.toList()")
visitorInterface.functions.add(visitNodesFunction) visitorExtensionSet.functions.add(visitNodesFunction)
val visitAllFunction = KotlinFunction( val visitAllFunction = KotlinFunction(
"visitAll", "visitAll",
typeParameters = mutableListOf("T"),
extensionOf = "NodeVisitor<T>",
returnType = "List<T>", returnType = "List<T>",
parameters = mutableListOf( parameters = mutableListOf(
KotlinParameter("nodeLists", type = "List<Node>", vararg = true) KotlinParameter("nodeLists", type = "List<Node>", vararg = true)
@ -93,25 +118,9 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
isImmediateExpression = true isImmediateExpression = true
) )
visitAllFunction.body.add("nodeLists.asSequence().flatten().map { visit(it) }.toList()") visitAllFunction.body.add("nodeLists.asSequence().flatten().map { visit(it) }.toList()")
visitorInterface.functions.add(visitAllFunction) visitorExtensionSet.functions.add(visitAllFunction)
val visitFunction = KotlinFunction( write("NodeVisitorExtensions.kt", KotlinWriter(visitorExtensionSet))
"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() { private fun writeNodeCoalescer() {
@ -193,10 +202,21 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
), ),
isImmediateExpression = true isImmediateExpression = true
) )
abstractVisitChildrenFunction.body.add("emptyList()") abstractVisitChildrenFunction.body.add("emptyList()")
kotlinClassLike.functions.add(abstractVisitChildrenFunction) 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) { } else if (role == AstTypeRole.AstNode) {
val typeMember = KotlinMember( val typeMember = KotlinMember(
"type", "type",
@ -245,21 +265,21 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
val visitChildrenFunction = KotlinFunction( val visitChildrenFunction = KotlinFunction(
"visitChildren", "visitChildren",
returnType = "List<T>", 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 anyListMembers = type.values.any { it.typeRef.form == AstTypeRefForm.List }
val elideVisitChildren: Boolean val elideVisitChildren: Boolean
if (anyListMembers) { if (anyListMembers) {
val visitParameters = type.values.mapNotNull { val visitParameters = type.values.mapNotNull {
if (it.typeRef.primitive != null) { if (it.typeRef.primitive != null) {
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 null
} else if (it.typeRef.form == AstTypeRefForm.Single) { } else if (it.typeRef.form == AstTypeRefForm.Single) {
"listOf(${it.name})" "listOf(${it.name})"
@ -273,7 +293,8 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
val visitParameters = type.values.mapNotNull { val visitParameters = type.values.mapNotNull {
if (it.typeRef.primitive != null) { if (it.typeRef.primitive != null) {
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 null
} else { } else {
it.name it.name
@ -287,7 +308,22 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
kotlinClassLike.functions.add(visitChildrenFunction) 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( val equalsFunction = KotlinFunction(
"equals", "equals",
returnType = "Boolean", returnType = "Boolean",
@ -348,9 +384,10 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
} }
private fun write(fileName: String, writer: KotlinWriter) { private fun write(fileName: String, writer: KotlinWriter) {
val content = "// GENERATED CODE FROM PORK AST CODEGEN\n$writer"
val path = outputDirectory.resolve(fileName) val path = outputDirectory.resolve(fileName)
path.deleteIfExists() path.deleteIfExists()
path.writeText(writer.toString(), StandardCharsets.UTF_8) path.writeText(content, StandardCharsets.UTF_8)
} }
companion object { companion object {

View File

@ -3,6 +3,7 @@ package gay.pizza.pork.buildext.codegen
class KotlinFunction( class KotlinFunction(
val name: String, val name: String,
var typeParameters: MutableList<String> = mutableListOf(), var typeParameters: MutableList<String> = mutableListOf(),
var extensionOf: String? = null,
var parameters: MutableList<KotlinParameter> = mutableListOf(), var parameters: MutableList<KotlinParameter> = mutableListOf(),
var returnType: String? = null, var returnType: String? = null,
var abstract: Boolean = false, 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() { class KotlinWriter() {
private val buffer = StringBuilder() private val buffer = StringBuilder()
constructor(kotlinClassLike: KotlinClassLike) : this() { constructor(writable: Any) : this() {
write(kotlinClassLike) write(writable)
} }
fun writeClass(kotlinClass: KotlinClass): Unit = buffer.run { fun writeClass(kotlinClass: KotlinClass): Unit = buffer.run {
@ -96,16 +96,7 @@ class KotlinWriter() {
classType: String, classType: String,
kotlinClass: KotlinClassLike kotlinClass: KotlinClassLike
): Unit = buffer.run { ): Unit = buffer.run {
appendLine("package ${kotlinClass.pkg}") writeHeader(kotlinClass.pkg, kotlinClass.imports)
appendLine()
for (import in kotlinClass.imports) {
appendLine("import $import")
}
if (kotlinClass.imports.isNotEmpty()) {
appendLine()
}
for (annotation in kotlinClass.annotations) { for (annotation in kotlinClass.annotations) {
appendLine("@${annotation}") appendLine("@${annotation}")
@ -138,76 +129,110 @@ class KotlinWriter() {
} }
} }
private fun writeFunctions(kotlinClassLike: KotlinClassLike): Unit = buffer.run { private fun writeHeader(pkg: String, imports: List<String>): Unit = buffer.run {
for ((index, function) in kotlinClassLike.functions.withIndex()) { appendLine("package $pkg")
append(" ") appendLine()
if (function.overridden) { for (import in imports) {
append("override ") appendLine("import $import")
} }
if (function.abstract) { if (imports.isNotEmpty()) {
append("abstract ") appendLine()
}
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()
}
} }
} }
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 KotlinClass -> writeClass(input)
is KotlinEnum -> writeEnum(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() 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.* import gay.pizza.pork.ast.*
class EvaluationVisitor(val root: Scope) : NodeVisitor<Any> { class EvaluationVisitor(root: Scope) : NodeVisitor<Any> {
private var currentScope: Scope = root private var currentScope: Scope = root
override fun visitIntLiteral(node: IntLiteral): Any = node.value override fun visitIntLiteral(node: IntLiteral): Any = node.value
override fun visitStringLiteral(node: StringLiteral): Any = node.text override fun visitStringLiteral(node: StringLiteral): Any = node.text
override fun visitBooleanLiteral(node: BooleanLiteral): Any = node.value 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 visitSymbol(node: Symbol): Any = None
override fun visitFunctionCall(node: FunctionCall): Any { 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)) return currentScope.call(node.symbol.id, Arguments(arguments))
} }
override fun visitLetAssignment(node: LetAssignment): Any { override fun visitLetAssignment(node: LetAssignment): Any {
val value = visit(node.value) val value = node.value.visit(this)
currentScope.define(node.symbol.id, value) currentScope.define(node.symbol.id, value)
return value return value
} }
@ -35,7 +36,7 @@ class EvaluationVisitor(val root: Scope) : NodeVisitor<Any> {
try { try {
var value: Any? = null var value: Any? = null
for (expression in node.expressions) { for (expression in node.expressions) {
value = visit(expression) value = expression.visit(this)
} }
value ?: None value ?: None
} finally { } 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 { override fun visitPrefixOperation(node: PrefixOperation): Any {
val value = visit(node.expression) val value = node.expression.visit(this)
return when (node.op) { return when (node.op) {
PrefixOperator.Negate -> { PrefixOperator.Negate -> {
if (value !is Boolean) { if (value !is Boolean) {
@ -59,21 +61,18 @@ class EvaluationVisitor(val root: Scope) : NodeVisitor<Any> {
} }
override fun visitIf(node: If): Any { override fun visitIf(node: If): Any {
val condition = visit(node.condition) val condition = node.condition.visit(this)
return if (condition == true) { return if (condition == true) {
visit(node.thenExpression) node.thenExpression.visit(this)
} else { } else {
if (node.elseExpression != null) { val elseExpression = node.elseExpression
visit(node.elseExpression!!) elseExpression?.visit(this) ?: None
} else {
None
}
} }
} }
override fun visitInfixOperation(node: InfixOperation): Any { override fun visitInfixOperation(node: InfixOperation): Any {
val left = visit(node.left) val left = node.left.visit(this)
val right = visit(node.right) val right = node.right.visit(this)
when (node.op) { when (node.op) {
InfixOperator.Equals -> { InfixOperator.Equals -> {
@ -101,28 +100,21 @@ class EvaluationVisitor(val root: Scope) : NodeVisitor<Any> {
} }
} }
override fun visitFunctionDefinition(node: FunctionDefinition): Any { override fun visitBlock(node: Block): BlockFunction = BlockFunction {
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 {
var value: Any? = null var value: Any? = null
for (expression in node.expressions) { for (expression in node.expressions) {
value = visit(expression) value = expression.visit(this)
} }
value ?: None 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 { override fun visitImportDeclaration(node: ImportDeclaration): Any {
throw RuntimeException( throw RuntimeException(
"Import declarations cannot be visited in an EvaluationVisitor. " + "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 import gay.pizza.pork.frontend.World
class Evaluator(val world: World, val scope: Scope) : EvaluationContextProvider { class Evaluator(val world: World, val scope: Scope) {
private val contexts = mutableMapOf<String, EvaluationContext>() private val contexts = mutableMapOf<String, CompilationUnitContext>()
fun evaluate(path: String): Scope { fun evaluate(path: String): Scope =
val context = provideEvaluationContext(path) context(path).externalScope
return context.externalRootScope
}
override fun provideEvaluationContext(path: String): EvaluationContext { fun context(path: String): CompilationUnitContext {
val unit = world.load(path) val unit = world.load(path)
val identity = world.contentSource.stableContentIdentity(path) val identity = world.contentSource.stableContentIdentity(path)
val context = contexts.computeIfAbsent(identity) { val context = contexts.computeIfAbsent(identity) {
EvaluationContext(unit, this, scope) CompilationUnitContext(unit, this, scope)
} }
context.init() context.initIfNeeded()
return context 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 { fun value(name: String): Any {
val value = valueOrNotFound(name) val value = valueOrNotFound(name)
if (value == NotFound) { if (value === NotFound) {
throw RuntimeException("Variable '${name}' not defined.") throw RuntimeException("Variable '${name}' not defined")
} }
return value return value
} }
@ -25,14 +25,14 @@ class Scope(val parent: Scope? = null, inherits: List<Scope> = emptyList()) {
if (value == null) { if (value == null) {
if (parent != null) { if (parent != null) {
val parentMaybeFound = parent.valueOrNotFound(name) val parentMaybeFound = parent.valueOrNotFound(name)
if (parentMaybeFound != NotFound) { if (parentMaybeFound !== NotFound) {
return parentMaybeFound return parentMaybeFound
} }
} }
for (inherit in inherited) { for (inherit in inherited) {
val inheritMaybeFound = inherit.valueOrNotFound(name) val inheritMaybeFound = inherit.valueOrNotFound(name)
if (inheritMaybeFound != NotFound) { if (inheritMaybeFound !== NotFound) {
return inheritMaybeFound return inheritMaybeFound
} }
} }

View File

@ -1,22 +1,22 @@
package gay.pizza.pork.frontend 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.CharSource
import gay.pizza.pork.parser.StringCharSource 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 = override fun loadAsCharSource(path: String): CharSource =
StringCharSource(asFsPath(path).readText()) StringCharSource(asFsPath(path).readString())
override fun stableContentIdentity(path: String): String = 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 fsPath = root.resolve(path)
val absoluteRootPath = root.absolutePathString() + root.fileSystem.separator val rootPathWithSeparator = root.fullPathString + PlatformFsProvider.separator
if (!fsPath.absolutePathString().startsWith(absoluteRootPath)) { if (!fsPath.fullPathString.startsWith(rootPathWithSeparator)) {
throw RuntimeException("Unable to load path outside of the root: $fsPath (root is ${root})") throw RuntimeException("Unable to load path outside of the root: $fsPath (root is ${root})")
} }
return fsPath return fsPath

View File

@ -2,6 +2,7 @@ package gay.pizza.pork.parser
import gay.pizza.pork.ast.NodeCoalescer import gay.pizza.pork.ast.NodeCoalescer
import gay.pizza.pork.ast.Node import gay.pizza.pork.ast.Node
import gay.pizza.pork.ast.visit
import java.util.IdentityHashMap import java.util.IdentityHashMap
class TokenNodeAttribution : NodeAttribution { 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.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument 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 gay.pizza.pork.ast.Node
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@OptIn(ExperimentalSerializationApi::class) @OptIn(ExperimentalSerializationApi::class)
class AstCommand : CliktCommand(help = "Print AST", name = "ast") { 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 { private val json = Json {
prettyPrint = true prettyPrint = true
@ -18,7 +18,7 @@ class AstCommand : CliktCommand(help = "Print AST", name = "ast") {
} }
override fun run() { override fun run() {
val tool = FileTool(path) val tool = FileTool(PlatformFsProvider.resolve(path))
println(json.encodeToString(Node.serializer(), tool.parse())) 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.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument 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.NodeCoalescer
import gay.pizza.pork.ast.visit
import gay.pizza.pork.parser.TokenNodeAttribution import gay.pizza.pork.parser.TokenNodeAttribution
class AttributeCommand : CliktCommand(help = "Attribute AST", name = "attribute") { 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() { override fun run() {
val tool = FileTool(path) val tool = FileTool(PlatformFsProvider.resolve(path))
val attribution = TokenNodeAttribution() val attribution = TokenNodeAttribution()
val compilationUnit = tool.parse(attribution) val compilationUnit = tool.parse(attribution)

View File

@ -1,15 +1,16 @@
package gay.pizza.pork.tool 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.ContentSource
import gay.pizza.pork.frontend.FsContentSource import gay.pizza.pork.frontend.FsContentSource
import gay.pizza.pork.parser.CharSource import gay.pizza.pork.parser.CharSource
import gay.pizza.pork.parser.StringCharSource 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() { class FileTool(val path: FsPath) : Tool() {
override fun createCharSource(): CharSource = StringCharSource(path.readText()) override fun createCharSource(): CharSource =
override fun createContentSource(): ContentSource = FsContentSource(path.absolute().parent) StringCharSource(path.readString())
override fun rootFilePath(): String = path.fileName.toString() 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.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument 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 import gay.pizza.pork.parser.AnsiHighlightScheme
class HighlightCommand : CliktCommand(help = "Syntax Highlighter", name = "highlight") { 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() { override fun run() {
val tool = FileTool(path) val tool = FileTool(PlatformFsProvider.resolve(path))
print(tool.highlight(AnsiHighlightScheme()).joinToString("")) 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.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument 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") { 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() { override fun run() {
val tool = FileTool(path) val tool = FileTool(PlatformFsProvider.resolve(path))
print(tool.reprint()) print(tool.reprint())
} }
} }

View File

@ -13,6 +13,7 @@ class RootCommand : CliktCommand(
HighlightCommand(), HighlightCommand(),
TokenizeCommand(), TokenizeCommand(),
ReprintCommand(), ReprintCommand(),
ParseCommand(),
AstCommand(), AstCommand(),
AttributeCommand() 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.arguments.argument
import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option 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.CallableFunction
import gay.pizza.pork.evaluator.None
import gay.pizza.pork.evaluator.Scope import gay.pizza.pork.evaluator.Scope
import kotlin.system.measureTimeMillis
class RunCommand : CliktCommand(help = "Run Program", name = "run") { class RunCommand : CliktCommand(help = "Run Program", name = "run") {
val loop by option("--loop", help = "Loop Program").flag() val loop by option("--loop", help = "Loop Program").flag()
val measure by option("--measure", help = "Measure Time").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() { override fun run() {
if (loop) { val tool = FileTool(PlatformFsProvider.resolve(path))
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 scope = Scope() val scope = Scope()
scope.define("println", CallableFunction { arguments -> scope.define("println", CallableFunction { arguments ->
if (quiet) {
return@CallableFunction None
}
for (argument in arguments.values) { for (argument in arguments.values) {
println(argument) 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.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument 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") { 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() { override fun run() {
val tool = FileTool(path) val tool = FileTool(PlatformFsProvider.resolve(path))
val tokenStream = tool.tokenize() val tokenStream = tool.tokenize()
for (token in tokenStream.tokens) { for (token in tokenStream.tokens) {
println("${token.start} ${token.type.name} '${sanitize(token.text)}'") 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.ast.NodeVisitor
import gay.pizza.pork.parser.Printer import gay.pizza.pork.parser.Printer
import gay.pizza.pork.ast.CompilationUnit import gay.pizza.pork.ast.CompilationUnit
import gay.pizza.pork.ast.visit
import gay.pizza.pork.evaluator.Arguments import gay.pizza.pork.evaluator.Arguments
import gay.pizza.pork.evaluator.Evaluator import gay.pizza.pork.evaluator.Evaluator
import gay.pizza.pork.evaluator.Scope import gay.pizza.pork.evaluator.Scope