while loop support, and native functions (including ffi!)

This commit is contained in:
Alex Zenla 2023-09-06 19:07:28 -07:00
parent ddff6cb365
commit 236f812caf
Signed by: alex
GPG Key ID: C0780728420EBFE5
34 changed files with 467 additions and 115 deletions

View File

@ -95,16 +95,18 @@ types:
- name: arguments - name: arguments
type: List<Symbol> type: List<Symbol>
- name: block - name: block
type: Block type: Block?
- name: native
type: Native?
If: If:
parent: Expression parent: Expression
values: values:
- name: condition - name: condition
type: Expression type: Expression
- name: thenExpression - name: thenBlock
type: Expression type: Block
- name: elseExpression - name: elseBlock
type: Expression? type: Block?
ImportDeclaration: ImportDeclaration:
parent: Declaration parent: Declaration
values: values:
@ -150,3 +152,23 @@ types:
values: values:
- name: symbol - name: symbol
type: Symbol type: Symbol
While:
parent: Expression
values:
- name: condition
type: Expression
- name: block
type: Block
Break:
parent: Expression
values: []
Continue:
parent: Expression
values: []
Native:
parent: Node
values:
- name: form
type: Symbol
- name: definition
type: StringLiteral

View File

@ -0,0 +1,22 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("break")
class Break : Expression() {
override val type: NodeType = NodeType.Break
override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitBreak(this)
override fun equals(other: Any?): Boolean {
if (other !is Break) return false
return true
}
override fun hashCode(): Int =
31 * type.hashCode()
}

View File

@ -0,0 +1,22 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("continue")
class Continue : Expression() {
override val type: NodeType = NodeType.Continue
override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitContinue(this)
override fun equals(other: Any?): Boolean {
if (other !is Continue) return false
return true
}
override fun hashCode(): Int =
31 * type.hashCode()
}

View File

@ -6,18 +6,18 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
@SerialName("functionDefinition") @SerialName("functionDefinition")
class FunctionDefinition(override val modifiers: DefinitionModifiers, override val symbol: Symbol, val arguments: List<Symbol>, val block: Block) : Definition() { class FunctionDefinition(override val modifiers: DefinitionModifiers, override val symbol: Symbol, val arguments: List<Symbol>, val block: Block?, val native: Native?) : Definition() {
override val type: NodeType = NodeType.FunctionDefinition override val type: NodeType = NodeType.FunctionDefinition
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), listOf(native))
override fun <T> visit(visitor: NodeVisitor<T>): T = override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitFunctionDefinition(this) 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 && other.native == native
} }
override fun hashCode(): Int { override fun hashCode(): Int {
@ -25,6 +25,7 @@ class FunctionDefinition(override val modifiers: DefinitionModifiers, override v
result = 31 * result + symbol.hashCode() result = 31 * result + symbol.hashCode()
result = 31 * result + arguments.hashCode() result = 31 * result + arguments.hashCode()
result = 31 * result + block.hashCode() result = 31 * result + block.hashCode()
result = 31 * result + native.hashCode()
result = 31 * result + type.hashCode() result = 31 * result + type.hashCode()
return result return result
} }

View File

@ -6,24 +6,24 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
@SerialName("if") @SerialName("if")
class If(val condition: Expression, val thenExpression: Expression, val elseExpression: Expression?) : Expression() { class If(val condition: Expression, val thenBlock: Block, val elseBlock: Block?) : Expression() {
override val type: NodeType = NodeType.If override val type: NodeType = NodeType.If
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, thenBlock, elseBlock)
override fun <T> visit(visitor: NodeVisitor<T>): T = override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitIf(this) 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.thenBlock == thenBlock && other.elseBlock == elseBlock
} }
override fun hashCode(): Int { override fun hashCode(): Int {
var result = condition.hashCode() var result = condition.hashCode()
result = 31 * result + thenExpression.hashCode() result = 31 * result + thenBlock.hashCode()
result = 31 * result + elseExpression.hashCode() result = 31 * result + elseBlock.hashCode()
result = 31 * result + type.hashCode() result = 31 * result + type.hashCode()
return result return result
} }

View File

@ -0,0 +1,29 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("native")
class Native(val form: Symbol, val definition: StringLiteral) : Node() {
override val type: NodeType = NodeType.Native
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
visitor.visitNodes(form, definition)
override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitNative(this)
override fun equals(other: Any?): Boolean {
if (other !is Native) return false
return other.form == form && other.definition == definition
}
override fun hashCode(): Int {
var result = form.hashCode()
result = 31 * result + definition.hashCode()
result = 31 * result + type.hashCode()
return result
}
}

View File

@ -8,9 +8,15 @@ class NodeCoalescer(val handler: (Node) -> Unit) : NodeVisitor<Unit> {
override fun visitBooleanLiteral(node: BooleanLiteral): Unit = override fun visitBooleanLiteral(node: BooleanLiteral): Unit =
handle(node) handle(node)
override fun visitBreak(node: Break): Unit =
handle(node)
override fun visitCompilationUnit(node: CompilationUnit): Unit = override fun visitCompilationUnit(node: CompilationUnit): Unit =
handle(node) handle(node)
override fun visitContinue(node: Continue): Unit =
handle(node)
override fun visitFunctionCall(node: FunctionCall): Unit = override fun visitFunctionCall(node: FunctionCall): Unit =
handle(node) handle(node)
@ -35,6 +41,9 @@ class NodeCoalescer(val handler: (Node) -> Unit) : NodeVisitor<Unit> {
override fun visitListLiteral(node: ListLiteral): Unit = override fun visitListLiteral(node: ListLiteral): Unit =
handle(node) handle(node)
override fun visitNative(node: Native): Unit =
handle(node)
override fun visitParentheses(node: Parentheses): Unit = override fun visitParentheses(node: Parentheses): Unit =
handle(node) handle(node)
@ -50,6 +59,9 @@ class NodeCoalescer(val handler: (Node) -> Unit) : NodeVisitor<Unit> {
override fun visitSymbolReference(node: SymbolReference): Unit = override fun visitSymbolReference(node: SymbolReference): Unit =
handle(node) handle(node)
override fun visitWhile(node: While): Unit =
handle(node)
fun handle(node: Node) { fun handle(node: Node) {
handler(node) handler(node)
node.visitChildren(this) node.visitChildren(this)

View File

@ -6,7 +6,9 @@ enum class NodeType(val parent: NodeType? = null) {
Block(Node), Block(Node),
Expression(Node), Expression(Node),
BooleanLiteral(Expression), BooleanLiteral(Expression),
Break(Expression),
CompilationUnit(Node), CompilationUnit(Node),
Continue(Expression),
Declaration(Node), Declaration(Node),
Definition(Node), Definition(Node),
FunctionCall(Expression), FunctionCall(Expression),
@ -17,9 +19,11 @@ enum class NodeType(val parent: NodeType? = null) {
IntLiteral(Expression), IntLiteral(Expression),
LetAssignment(Expression), LetAssignment(Expression),
ListLiteral(Expression), ListLiteral(Expression),
Native(Node),
Parentheses(Expression), Parentheses(Expression),
PrefixOperation(Expression), PrefixOperation(Expression),
StringLiteral(Expression), StringLiteral(Expression),
Symbol(Node), Symbol(Node),
SymbolReference(Expression) SymbolReference(Expression),
While(Expression)
} }

View File

@ -6,8 +6,12 @@ interface NodeVisitor<T> {
fun visitBooleanLiteral(node: BooleanLiteral): T fun visitBooleanLiteral(node: BooleanLiteral): T
fun visitBreak(node: Break): T
fun visitCompilationUnit(node: CompilationUnit): T fun visitCompilationUnit(node: CompilationUnit): T
fun visitContinue(node: Continue): T
fun visitFunctionCall(node: FunctionCall): T fun visitFunctionCall(node: FunctionCall): T
fun visitFunctionDefinition(node: FunctionDefinition): T fun visitFunctionDefinition(node: FunctionDefinition): T
@ -24,6 +28,8 @@ interface NodeVisitor<T> {
fun visitListLiteral(node: ListLiteral): T fun visitListLiteral(node: ListLiteral): T
fun visitNative(node: Native): T
fun visitParentheses(node: Parentheses): T fun visitParentheses(node: Parentheses): T
fun visitPrefixOperation(node: PrefixOperation): T fun visitPrefixOperation(node: PrefixOperation): T
@ -33,4 +39,6 @@ interface NodeVisitor<T> {
fun visitSymbol(node: Symbol): T fun visitSymbol(node: Symbol): T
fun visitSymbolReference(node: SymbolReference): T fun visitSymbolReference(node: SymbolReference): T
fun visitWhile(node: While): T
} }

View File

@ -19,10 +19,14 @@ fun <T> NodeVisitor<T>.visit(node: Node): T =
is PrefixOperation -> visitPrefixOperation(node) is PrefixOperation -> visitPrefixOperation(node)
is StringLiteral -> visitStringLiteral(node) is StringLiteral -> visitStringLiteral(node)
is SymbolReference -> visitSymbolReference(node) is SymbolReference -> visitSymbolReference(node)
is While -> visitWhile(node)
is Break -> visitBreak(node)
is Continue -> visitContinue(node)
is Native -> visitNative(node)
} }
fun <T> NodeVisitor<T>.visitNodes(vararg nodes: Node?): List<T> = fun <T> NodeVisitor<T>.visitNodes(vararg nodes: Node?): List<T> =
nodes.asSequence().filterNotNull().map { visit(it) }.toList() nodes.asSequence().filterNotNull().map { visit(it) }.toList()
fun <T> NodeVisitor<T>.visitAll(vararg nodeLists: List<Node>): List<T> = fun <T> NodeVisitor<T>.visitAll(vararg nodeLists: List<Node?>): List<T> =
nodeLists.asSequence().flatten().map { visit(it) }.toList() nodeLists.asSequence().flatten().filterNotNull().map { visit(it) }.toList()

View File

@ -0,0 +1,29 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("while")
class While(val condition: Expression, val block: Block) : Expression() {
override val type: NodeType = NodeType.While
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
visitor.visitNodes(condition, block)
override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitWhile(this)
override fun equals(other: Any?): Boolean {
if (other !is While) return false
return other.condition == condition && other.block == block
}
override fun hashCode(): Int {
var result = condition.hashCode()
result = 31 * result + block.hashCode()
result = 31 * result + type.hashCode()
return result
}
}

View File

@ -120,11 +120,12 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
extensionOf = "NodeVisitor<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)
), ),
isImmediateExpression = true isImmediateExpression = true
) )
visitAllFunction.body.add("nodeLists.asSequence().flatten().map { visit(it) }.toList()") visitAllFunction.body.add(
"nodeLists.asSequence().flatten().filterNotNull().map { visit(it) }.toList()")
visitorExtensionSet.functions.add(visitAllFunction) visitorExtensionSet.functions.add(visitAllFunction)
write("NodeVisitorExtensions.kt", KotlinWriter(visitorExtensionSet)) write("NodeVisitorExtensions.kt", KotlinWriter(visitorExtensionSet))
@ -239,16 +240,18 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
kotlinClassLike.inherits.add("$parentName()") kotlinClassLike.inherits.add("$parentName()")
} }
for (value in type.values) { if (type.values != null) {
val member = KotlinMember(value.name, toKotlinType(value.typeRef)) for (value in type.values!!) {
member.abstract = value.abstract val member = KotlinMember(value.name, toKotlinType(value.typeRef))
if (type.isParentAbstract(value)) { member.abstract = value.abstract
member.overridden = true if (type.isParentAbstract(value)) {
member.overridden = true
}
if (role == AstTypeRole.ValueHolder) {
member.mutable = true
}
kotlinClassLike.members.add(member)
} }
if (role == AstTypeRole.ValueHolder) {
member.mutable = true
}
kotlinClassLike.members.add(member)
} }
if (role == AstTypeRole.Enum) { if (role == AstTypeRole.Enum) {
@ -279,25 +282,26 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
), ),
isImmediateExpression = true isImmediateExpression = true
) )
val anyListMembers = type.values.any { it.typeRef.form == AstTypeRefForm.List } val anyListMembers = type.values?.any { it.typeRef.form == AstTypeRefForm.List } ?: false
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 && } else if (it.typeRef.type != null &&
!world.typeRegistry.roleOfType(it.typeRef.type).isNodeInherited()) { !world.typeRegistry.roleOfType(it.typeRef.type).isNodeInherited()) {
null null
} else if (it.typeRef.form == AstTypeRefForm.Single) { } else if (it.typeRef.form == AstTypeRefForm.Single ||
it.typeRef.form == AstTypeRefForm.Nullable) {
"listOf(${it.name})" "listOf(${it.name})"
} else { } else {
it.name it.name
} }
}.joinToString(", ") } ?: emptyList()).joinToString(", ")
elideVisitChildren = visitParameters.isEmpty() elideVisitChildren = visitParameters.isEmpty()
visitChildrenFunction.body.add("visitor.visitAll(${visitParameters})") visitChildrenFunction.body.add("visitor.visitAll(${visitParameters})")
} else { } else {
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 && } else if (it.typeRef.type != null &&
@ -306,7 +310,7 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
} else { } else {
it.name it.name
} }
}.joinToString(", ") } ?: emptyList()).joinToString(", ")
elideVisitChildren = visitParameters.isEmpty() elideVisitChildren = visitParameters.isEmpty()
visitChildrenFunction.body.add("visitor.visitNodes(${visitParameters})") visitChildrenFunction.body.add("visitor.visitNodes(${visitParameters})")
} }
@ -341,9 +345,12 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld
"Any?" "Any?"
)) ))
equalsFunction.body.add("if (other !is ${type.name}) return false") equalsFunction.body.add("if (other !is ${type.name}) return false")
val predicate = equalsAndHashCodeMembers.mapNotNull { var predicate = equalsAndHashCodeMembers.mapNotNull {
if (it == "type") null else "other.${it} == $it" if (it == "type") null else "other.${it} == $it"
}.joinToString(" && ") }.joinToString(" && ")
if (predicate.isEmpty()) {
predicate = "true"
}
equalsFunction.body.add("return $predicate") equalsFunction.body.add("return $predicate")
kotlinClassLike.functions.add(equalsFunction) kotlinClassLike.functions.add(equalsFunction)

View File

@ -1,17 +1,24 @@
package gay.pizza.pork.buildext.ast package gay.pizza.pork.buildext.ast
class AstType(val name: String, var parent: AstType? = null) { class AstType(val name: String, var parent: AstType? = null) {
private val internalValues = mutableListOf<AstValue>() private var internalValues: MutableList<AstValue>? = null
private val internalEnums = mutableListOf<AstEnum>() private val internalEnums = mutableListOf<AstEnum>()
val values: List<AstValue> val values: List<AstValue>?
get() = internalValues get() = internalValues
val enums: List<AstEnum> val enums: List<AstEnum>
get() = internalEnums get() = internalEnums
internal fun markHasValues() {
if (internalValues == null) {
internalValues = mutableListOf()
}
}
internal fun addValue(value: AstValue) { internal fun addValue(value: AstValue) {
internalValues.add(value) markHasValues()
internalValues!!.add(value)
} }
internal fun addEnum(enum: AstEnum) { internal fun addEnum(enum: AstEnum) {
@ -25,7 +32,7 @@ class AstType(val name: String, var parent: AstType? = null) {
var current = parent var current = parent
while (current != null) { while (current != null) {
val abstract = current.values.firstOrNull { val abstract = current.values?.firstOrNull {
it.name == value.name && it.abstract it.name == value.name && it.abstract
} }
if (abstract != null) { if (abstract != null) {

View File

@ -2,6 +2,6 @@ package gay.pizza.pork.buildext.ast
data class AstTypeDescription( data class AstTypeDescription(
val parent: String? = null, val parent: String? = null,
val values: List<AstValueDescription> = emptyList(), val values: List<AstValueDescription>? = null,
val enums: List<AstEnumDescription> = emptyList() val enums: List<AstEnumDescription> = emptyList()
) )

View File

@ -21,13 +21,14 @@ class AstTypeRegistry {
when { when {
type.enums.isNotEmpty() -> type.enums.isNotEmpty() ->
AstTypeRole.Enum AstTypeRole.Enum
type.parent == null && type.values.isEmpty() -> type.parent == null && type.values == null ->
AstTypeRole.RootNode AstTypeRole.RootNode
type.parent != null && type.values.all { it.abstract } -> type.parent != null && (type.values == null ||
(type.values!!.isNotEmpty() && type.values!!.all { it.abstract })) ->
AstTypeRole.HierarchyNode AstTypeRole.HierarchyNode
type.parent != null && type.values.none { it.abstract } -> type.parent != null && (type.values != null && type.values!!.none { it.abstract }) ->
AstTypeRole.AstNode AstTypeRole.AstNode
type.parent == null && type.values.isNotEmpty() -> type.parent == null && (type.values != null && type.values!!.isNotEmpty()) ->
AstTypeRole.ValueHolder AstTypeRole.ValueHolder
else -> throw RuntimeException("Unable to determine role of type ${type.name}") else -> throw RuntimeException("Unable to determine role of type ${type.name}")
} }

View File

@ -39,10 +39,13 @@ class AstWorld {
type.parent = world.typeRegistry.lookup(typeDescription.parent) type.parent = world.typeRegistry.lookup(typeDescription.parent)
} }
for (value in typeDescription.values) { if (typeDescription.values != null) {
val typeRef = AstTypeRef.parse(value.type, world.typeRegistry) type.markHasValues()
val typeValue = AstValue(value.name, typeRef, abstract = value.required) for (value in typeDescription.values) {
type.addValue(typeValue) val typeRef = AstTypeRef.parse(value.type, world.typeRegistry)
val typeValue = AstValue(value.name, typeRef, abstract = value.required)
type.addValue(typeValue)
}
} }
for (enum in typeDescription.enums) { for (enum in typeDescription.enums) {

View File

@ -39,7 +39,7 @@ class CompilationUnitContext(
} }
private fun definitionValue(definition: Definition): Any = when (definition) { private fun definitionValue(definition: Definition): Any = when (definition) {
is FunctionDefinition -> FunctionContext(definition, internalScope) is FunctionDefinition -> FunctionContext(this, definition)
} }
private fun processAllImports() { private fun processAllImports() {

View File

@ -8,6 +8,9 @@ class EvaluationVisitor(root: Scope) : NodeVisitor<Any> {
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 visitBreak(node: Break): Any = throw BreakMarker
override fun visitListLiteral(node: ListLiteral): Any = override fun visitListLiteral(node: ListLiteral): Any =
node.items.map { it.visit(this) } node.items.map { it.visit(this) }
@ -15,7 +18,8 @@ class EvaluationVisitor(root: Scope) : NodeVisitor<Any> {
override fun visitFunctionCall(node: FunctionCall): Any { override fun visitFunctionCall(node: FunctionCall): Any {
val arguments = node.arguments.map { it.visit(this) } val arguments = node.arguments.map { it.visit(this) }
return currentScope.call(node.symbol.id, Arguments(arguments)) val functionValue = currentScope.value(node.symbol.id) as CallableFunction
return functionValue.call(Arguments(arguments))
} }
override fun visitLetAssignment(node: LetAssignment): Any { override fun visitLetAssignment(node: LetAssignment): Any {
@ -27,6 +31,26 @@ class EvaluationVisitor(root: Scope) : NodeVisitor<Any> {
override fun visitSymbolReference(node: SymbolReference): Any = override fun visitSymbolReference(node: SymbolReference): Any =
currentScope.value(node.symbol.id) currentScope.value(node.symbol.id)
override fun visitWhile(node: While): Any {
val blockFunction = node.block.visit(this) as BlockFunction
var result: Any? = null
while (true) {
val value = node.condition.visit(this)
if (value !is Boolean) {
throw RuntimeException("While loop attempted on non-boolean value: $value")
}
if (!value) break
try {
scoped { result = blockFunction.call() }
} catch (_: BreakMarker) {
break
} catch (_: ContinueMarker) {
continue
}
}
return result ?: None
}
override fun visitParentheses(node: Parentheses): Any = override fun visitParentheses(node: Parentheses): Any =
node.expression.visit(this) node.expression.visit(this)
@ -45,11 +69,12 @@ class EvaluationVisitor(root: Scope) : NodeVisitor<Any> {
override fun visitIf(node: If): Any { override fun visitIf(node: If): Any {
val condition = node.condition.visit(this) val condition = node.condition.visit(this)
return if (condition == true) { return if (condition == true) {
node.thenExpression.visit(this) val blockFunction = node.thenBlock.visit(this) as BlockFunction
} else { scoped { blockFunction.call() }
val elseExpression = node.elseExpression } else if (node.elseBlock != null) {
elseExpression?.visit(this) ?: None val blockFunction = node.elseBlock!!.visit(this) as BlockFunction
} scoped { blockFunction.call() }
} else None
} }
override fun visitInfixOperation(node: InfixOperation): Any { override fun visitInfixOperation(node: InfixOperation): Any {
@ -93,21 +118,42 @@ class EvaluationVisitor(root: Scope) : NodeVisitor<Any> {
override fun visitFunctionDefinition(node: FunctionDefinition): Any { override fun visitFunctionDefinition(node: FunctionDefinition): Any {
throw RuntimeException( throw RuntimeException(
"Function declarations cannot be visited in an EvaluationVisitor. " + "Function declarations cannot be visited in an EvaluationVisitor. " +
"Utilize a FunctionContext." "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. " +
"Utilize an EvaluationContext." "Utilize an CompilationUnitContext."
) )
} }
override fun visitCompilationUnit(node: CompilationUnit): Any { override fun visitCompilationUnit(node: CompilationUnit): Any {
throw RuntimeException( throw RuntimeException(
"Compilation units cannot be visited in an EvaluationVisitor. " + "Compilation units cannot be visited in an EvaluationVisitor. " +
"Utilize an EvaluationContext." "Utilize an CompilationUnitContext."
) )
} }
override fun visitNative(node: Native): Any {
throw RuntimeException(
"Native definition cannot be visited in an EvaluationVisitor. " +
"Utilize an FunctionContext."
)
}
override fun visitContinue(node: Continue): Any = ContinueMarker
private inline fun <T> scoped(block: () -> T): T {
currentScope = currentScope.fork()
try {
return block()
} finally {
currentScope = currentScope.leave()
}
}
private object BreakMarker : RuntimeException("Break Marker")
private object ContinueMarker: RuntimeException("Continue Marker")
} }

View File

@ -4,6 +4,7 @@ import gay.pizza.pork.frontend.World
class Evaluator(val world: World, val scope: Scope) { class Evaluator(val world: World, val scope: Scope) {
private val contexts = mutableMapOf<String, CompilationUnitContext>() private val contexts = mutableMapOf<String, CompilationUnitContext>()
private val nativeFunctionProviders = mutableMapOf<String, NativeFunctionProvider>()
fun evaluate(path: String): Scope = fun evaluate(path: String): Scope =
context(path).externalScope context(path).externalScope
@ -17,4 +18,13 @@ class Evaluator(val world: World, val scope: Scope) {
context.initIfNeeded() context.initIfNeeded()
return context return context
} }
fun nativeFunctionProvider(form: String): NativeFunctionProvider {
return nativeFunctionProviders[form] ?:
throw RuntimeException("Unknown native function form: $form")
}
fun addNativeFunctionProvider(form: String, nativeFunctionProvider: NativeFunctionProvider) {
nativeFunctionProviders[form] = nativeFunctionProvider
}
} }

View File

@ -2,14 +2,27 @@ package gay.pizza.pork.evaluator
import gay.pizza.pork.ast.FunctionDefinition import gay.pizza.pork.ast.FunctionDefinition
class FunctionContext(val node: FunctionDefinition, val internalScope: Scope) : CallableFunction { class FunctionContext(val compilationUnitContext: CompilationUnitContext, val node: FunctionDefinition) : CallableFunction {
override fun call(arguments: Arguments): Any { override fun call(arguments: Arguments): Any {
val scope = internalScope.fork() val scope = compilationUnitContext.internalScope.fork()
for ((index, argumentSymbol) in node.arguments.withIndex()) { for ((index, argumentSymbol) in node.arguments.withIndex()) {
scope.define(argumentSymbol.id, arguments.values[index]) scope.define(argumentSymbol.id, arguments.values[index])
} }
if (node.native != null) {
val native = node.native!!
val nativeFunctionProvider =
compilationUnitContext.evaluator.nativeFunctionProvider(native.form.id)
val nativeFunction = nativeFunctionProvider.provideNativeFunction(native.definition.text)
return nativeFunction.call(arguments)
}
if (node.block == null) {
throw RuntimeException("Native or Block is required for FunctionDefinition")
}
val visitor = EvaluationVisitor(scope) val visitor = EvaluationVisitor(scope)
val blockFunction = visitor.visitBlock(node.block) val blockFunction = visitor.visitBlock(node.block!!)
return blockFunction.call() return blockFunction.call()
} }
} }

View File

@ -0,0 +1,5 @@
package gay.pizza.pork.evaluator
interface NativeFunctionProvider {
fun provideNativeFunction(definition: String): CallableFunction
}

View File

@ -41,27 +41,19 @@ class Scope(val parent: Scope? = null, inherits: List<Scope> = emptyList()) {
return value return value
} }
fun call(name: String, arguments: Arguments): Any {
val value = value(name)
if (value !is CallableFunction) {
throw RuntimeException("$value is not callable")
}
return value.call(arguments)
}
fun fork(): Scope = fun fork(): Scope =
Scope(this) Scope(this)
fun leave(): Scope {
if (parent == null) {
throw RuntimeException("Parent context not found")
}
return parent
}
internal fun inherit(scope: Scope) { internal fun inherit(scope: Scope) {
inherited.add(scope) inherited.add(scope)
} }
fun leave(): Scope {
if (parent == null) {
throw RuntimeException("Attempted to leave the root scope!")
}
return parent
}
private object NotFound private object NotFound
} }

13
examples/ffi.pork Normal file
View File

@ -0,0 +1,13 @@
func malloc(size)
native ffi "libc.dylib:malloc:void*"
func free(pointer)
native ffi "libc.dylib:free:void"
export func main() {
while true {
let pointer = malloc(8192)
println(pointer)
free(pointer)
}
}

View File

@ -1,10 +1,14 @@
/* fibonacci sequence */ /* fibonacci sequence */
func fib(n) { func fib(n) {
if n == 0 if n == 0 {
then 0 0
else if n == 1 } else {
then 1 if n == 1 {
else fib(n - 1) + fib(n - 2) 1
} else {
fib(n - 1) + fib(n - 2)
}
}
} }
export func main() { export func main() {

6
examples/loop.pork Normal file
View File

@ -0,0 +1,6 @@
export func main() {
while true {
println("Hello World")
break
}
}

10
ffi/build.gradle.kts Normal file
View File

@ -0,0 +1,10 @@
plugins {
id("gay.pizza.pork.module")
}
dependencies {
api(project(":evaluator"))
implementation(project(":common"))
implementation("net.java.dev.jna:jna:5.13.0")
}

View File

@ -0,0 +1,26 @@
package gay.pizza.pork.ffi
import com.sun.jna.Function
import gay.pizza.pork.evaluator.CallableFunction
import gay.pizza.pork.evaluator.NativeFunctionProvider
class JnaNativeProvider : NativeFunctionProvider {
override fun provideNativeFunction(definition: String): CallableFunction {
val (libraryName, functionSymbol, returnType, _) =
definition.split(":", limit = 3)
val function = Function.getFunction(libraryName, functionSymbol)
return CallableFunction {
return@CallableFunction invoke(function, it.values.toTypedArray(), returnType)
}
}
private fun invoke(function: Function, values: Array<Any>, type: String): Any = when (type) {
"void*" -> function.invokePointer(values)
"int" -> function.invokeInt(values)
"long" -> function.invokeLong(values)
"float" -> function.invokeFloat(values)
"double" -> function.invokeDouble(values)
"void" -> function.invokeVoid(values)
else -> throw RuntimeException("Unsupported ffi return type: $type")
}
}

View File

@ -72,13 +72,26 @@ class Parser(source: PeekableSource<Token>, val attribution: NodeAttribution) {
private fun readIf(): If = within { private fun readIf(): If = within {
expect(TokenType.If) expect(TokenType.If)
val condition = readExpression() val condition = readExpression()
expect(TokenType.Then) val thenBlock = readBlock()
val thenExpression = readExpression() var elseBlock: Block? = null
var elseExpression: Expression? = null
if (next(TokenType.Else)) { if (next(TokenType.Else)) {
elseExpression = readExpression() elseBlock = readBlock()
} }
If(condition, thenExpression, elseExpression) If(condition, thenBlock, elseBlock)
}
private fun readWhile(): While = within {
expect(TokenType.While)
val condition = readExpression()
val block = readBlock()
While(condition, block)
}
private fun readNative(): Native = within {
expect(TokenType.Native)
val form = readSymbolRaw()
val definition = readStringLiteral()
Native(form, definition)
} }
fun readExpression(): Expression { fun readExpression(): Expression {
@ -120,6 +133,20 @@ class Parser(source: PeekableSource<Token>, val attribution: NodeAttribution) {
readIf() readIf()
} }
TokenType.While -> {
readWhile()
}
TokenType.Break -> {
expect(TokenType.Break)
Break()
}
TokenType.Continue -> {
expect(TokenType.Continue)
Continue()
}
else -> { else -> {
throw RuntimeException( throw RuntimeException(
"Failed to parse token: ${token.type} '${token.text}' as" + "Failed to parse token: ${token.type} '${token.text}' as" +
@ -178,7 +205,15 @@ class Parser(source: PeekableSource<Token>, val attribution: NodeAttribution) {
readSymbolRaw() readSymbolRaw()
} }
expect(TokenType.RightParentheses) expect(TokenType.RightParentheses)
FunctionDefinition(modifiers, name, arguments, readBlock())
var native: Native? = null
var block: Block? = null
if (peek(TokenType.Native)) {
native = readNative()
} else {
block = readBlock()
}
FunctionDefinition(modifiers, name, arguments, block, native)
} }
private fun maybeReadDefinition(): Definition? { private fun maybeReadDefinition(): Definition? {

View File

@ -38,6 +38,8 @@ class Printer(buffer: StringBuilder) : NodeVisitor<Unit> {
} }
} }
override fun visitBreak(node: Break): Unit = append("break")
override fun visitListLiteral(node: ListLiteral) { override fun visitListLiteral(node: ListLiteral) {
append("[") append("[")
if (node.items.isNotEmpty()) { if (node.items.isNotEmpty()) {
@ -55,6 +57,13 @@ class Printer(buffer: StringBuilder) : NodeVisitor<Unit> {
append("]") append("]")
} }
override fun visitNative(node: Native) {
append("native ")
visit(node.form)
append(" ")
visit(node.definition)
}
override fun visitSymbol(node: Symbol) { override fun visitSymbol(node: Symbol) {
append(node.id) append(node.id)
} }
@ -82,6 +91,13 @@ class Printer(buffer: StringBuilder) : NodeVisitor<Unit> {
visit(node.symbol) visit(node.symbol)
} }
override fun visitWhile(node: While) {
append("while ")
visit(node.condition)
append(" ")
visit(node.block)
}
override fun visitParentheses(node: Parentheses) { override fun visitParentheses(node: Parentheses) {
append("(") append("(")
visit(node.expression) visit(node.expression)
@ -96,18 +112,13 @@ class Printer(buffer: StringBuilder) : NodeVisitor<Unit> {
override fun visitIf(node: If) { override fun visitIf(node: If) {
append("if ") append("if ")
visit(node.condition) visit(node.condition)
append(" then") append(" ")
out.increaseIndent() visit(node.thenBlock)
appendLine() if (node.elseBlock != null) {
visit(node.thenExpression) append(" ")
out.decreaseIndent()
if (node.elseExpression != null) {
appendLine()
append("else") append("else")
out.increaseIndent() append(" ")
appendLine() visit(node.elseBlock!!)
visit(node.elseExpression!!)
out.decreaseIndent()
} }
} }
@ -120,7 +131,7 @@ class Printer(buffer: StringBuilder) : NodeVisitor<Unit> {
} }
override fun visitFunctionDefinition(node: FunctionDefinition) { override fun visitFunctionDefinition(node: FunctionDefinition) {
append("fn ") append("func ")
visit(node.symbol) visit(node.symbol)
append("(") append("(")
for ((index, argument) in node.arguments.withIndex()) { for ((index, argument) in node.arguments.withIndex()) {
@ -130,7 +141,13 @@ class Printer(buffer: StringBuilder) : NodeVisitor<Unit> {
} }
} }
append(") ") append(") ")
visit(node.block) if (node.block != null) {
visit(node.block!!)
}
if (node.native != null) {
visit(node.native!!)
}
} }
override fun visitBlock(node: Block) { override fun visitBlock(node: Block) {
@ -167,4 +184,6 @@ class Printer(buffer: StringBuilder) : NodeVisitor<Unit> {
appendLine() appendLine()
} }
} }
override fun visitContinue(node: Continue): Unit = append("continue")
} }

View File

@ -25,11 +25,14 @@ enum class TokenType(vararg properties: TokenTypeProperty) {
False(Keyword("false"), KeywordFamily), False(Keyword("false"), KeywordFamily),
True(Keyword("true"), KeywordFamily), True(Keyword("true"), KeywordFamily),
If(Keyword("if"), KeywordFamily), If(Keyword("if"), KeywordFamily),
Then(Keyword("then"), KeywordFamily),
Else(Keyword("else"), KeywordFamily), Else(Keyword("else"), KeywordFamily),
While(Keyword("while"), KeywordFamily),
Continue(Keyword("continue"), KeywordFamily),
Break(Keyword("break"), KeywordFamily),
Import(Keyword("import"), KeywordFamily), Import(Keyword("import"), KeywordFamily),
Export(Keyword("export"), KeywordFamily), Export(Keyword("export"), KeywordFamily),
Func(Keyword("func"), KeywordFamily), Func(Keyword("func"), KeywordFamily),
Native(Keyword("native"), KeywordFamily),
Let(Keyword("let"), KeywordFamily), Let(Keyword("let"), KeywordFamily),
Whitespace(CharConsumer { it == ' ' || it == '\r' || it == '\n' || it == '\t' }), Whitespace(CharConsumer { it == ' ' || it == '\r' || it == '\n' || it == '\t' }),
BlockComment(CommentFamily), BlockComment(CommentFamily),

View File

@ -8,16 +8,6 @@ include(
":parser", ":parser",
":frontend", ":frontend",
":evaluator", ":evaluator",
":ffi",
":tool" ":tool"
) )
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
version("clikt", "4.2.0")
library("clikt", "com.github.ajalt.clikt", "clikt")
.versionRef("clikt")
}
}
}

View File

@ -10,7 +10,8 @@ dependencies {
api(project(":parser")) api(project(":parser"))
api(project(":frontend")) api(project(":frontend"))
api(project(":evaluator")) api(project(":evaluator"))
implementation(libs.clikt) api(project(":ffi"))
api("com.github.ajalt.clikt:clikt:4.2.0")
implementation(project(":common")) implementation(project(":common"))
} }

View File

@ -5,9 +5,11 @@ 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 gay.pizza.dough.fs.PlatformFsProvider import gay.pizza.dough.fs.PlatformFsProvider
import gay.pizza.pork.evaluator.Arguments
import gay.pizza.pork.evaluator.CallableFunction import gay.pizza.pork.evaluator.CallableFunction
import gay.pizza.pork.evaluator.None import gay.pizza.pork.evaluator.None
import gay.pizza.pork.evaluator.Scope import gay.pizza.pork.evaluator.Scope
import gay.pizza.pork.ffi.JnaNativeProvider
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()
@ -28,8 +30,12 @@ class RunCommand : CliktCommand(help = "Run Program", name = "run") {
None None
}) })
val main = tool.loadMainFunction(scope, setupEvaluator = {
addNativeFunctionProvider("ffi", JnaNativeProvider())
})
maybeLoopAndMeasure(loop, measure) { maybeLoopAndMeasure(loop, measure) {
tool.evaluate(scope) main.call(Arguments(emptyList()))
} }
} }
} }

View File

@ -5,6 +5,7 @@ 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.ast.visit
import gay.pizza.pork.evaluator.Arguments import gay.pizza.pork.evaluator.Arguments
import gay.pizza.pork.evaluator.CallableFunction
import gay.pizza.pork.evaluator.Evaluator import gay.pizza.pork.evaluator.Evaluator
import gay.pizza.pork.evaluator.Scope import gay.pizza.pork.evaluator.Scope
import gay.pizza.pork.frontend.ContentSource import gay.pizza.pork.frontend.ContentSource
@ -29,11 +30,12 @@ abstract class Tool {
fun <T> visit(visitor: NodeVisitor<T>): T = visitor.visit(parse()) fun <T> visit(visitor: NodeVisitor<T>): T = visitor.visit(parse())
fun evaluate(scope: Scope) { fun loadMainFunction(scope: Scope, setupEvaluator: Evaluator.() -> Unit = {}): CallableFunction {
val contentSource = createContentSource() val contentSource = createContentSource()
val world = World(contentSource) val world = World(contentSource)
val evaluator = Evaluator(world, scope) val evaluator = Evaluator(world, scope)
setupEvaluator(evaluator)
val resultingScope = evaluator.evaluate(rootFilePath()) val resultingScope = evaluator.evaluate(rootFilePath())
resultingScope.call("main", Arguments(emptyList())) return resultingScope.value("main") as CallableFunction
} }
} }