From 236f812caf746dd4e15b15648cc53b168bda1980 Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Wed, 6 Sep 2023 19:07:28 -0700 Subject: [PATCH] while loop support, and native functions (including ffi!) --- ast/src/main/ast/pork.yml | 32 ++++++++-- .../main/kotlin/gay/pizza/pork/ast/Break.kt | 22 +++++++ .../kotlin/gay/pizza/pork/ast/Continue.kt | 22 +++++++ .../gay/pizza/pork/ast/FunctionDefinition.kt | 7 +- ast/src/main/kotlin/gay/pizza/pork/ast/If.kt | 10 +-- .../main/kotlin/gay/pizza/pork/ast/Native.kt | 29 +++++++++ .../gay/pizza/pork/ast/NodeCoalescer.kt | 12 ++++ .../kotlin/gay/pizza/pork/ast/NodeType.kt | 6 +- .../kotlin/gay/pizza/pork/ast/NodeVisitor.kt | 8 +++ .../pizza/pork/ast/NodeVisitorExtensions.kt | 8 ++- .../main/kotlin/gay/pizza/pork/ast/While.kt | 29 +++++++++ .../gay/pizza/pork/buildext/ast/AstCodegen.kt | 43 +++++++------ .../gay/pizza/pork/buildext/ast/AstType.kt | 15 +++-- .../pork/buildext/ast/AstTypeDescription.kt | 2 +- .../pork/buildext/ast/AstTypeRegistry.kt | 9 +-- .../gay/pizza/pork/buildext/ast/AstWorld.kt | 11 ++-- .../pork/evaluator/CompilationUnitContext.kt | 2 +- .../pizza/pork/evaluator/EvaluationVisitor.kt | 64 ++++++++++++++++--- .../gay/pizza/pork/evaluator/Evaluator.kt | 10 +++ .../pizza/pork/evaluator/FunctionContext.kt | 19 +++++- .../pork/evaluator/NativeFunctionProvider.kt | 5 ++ .../kotlin/gay/pizza/pork/evaluator/Scope.kt | 22 ++----- examples/ffi.pork | 13 ++++ examples/fib.pork | 14 ++-- examples/loop.pork | 6 ++ ffi/build.gradle.kts | 10 +++ .../gay/pizza/pork/ffi/JnaNativeProvider.kt | 26 ++++++++ .../kotlin/gay/pizza/pork/parser/Parser.kt | 47 ++++++++++++-- .../kotlin/gay/pizza/pork/parser/Printer.kt | 45 +++++++++---- .../kotlin/gay/pizza/pork/parser/TokenType.kt | 5 +- settings.gradle.kts | 12 +--- tool/build.gradle.kts | 3 +- .../kotlin/gay/pizza/pork/tool/RunCommand.kt | 8 ++- .../main/kotlin/gay/pizza/pork/tool/Tool.kt | 6 +- 34 files changed, 467 insertions(+), 115 deletions(-) create mode 100644 ast/src/main/kotlin/gay/pizza/pork/ast/Break.kt create mode 100644 ast/src/main/kotlin/gay/pizza/pork/ast/Continue.kt create mode 100644 ast/src/main/kotlin/gay/pizza/pork/ast/Native.kt create mode 100644 ast/src/main/kotlin/gay/pizza/pork/ast/While.kt create mode 100644 evaluator/src/main/kotlin/gay/pizza/pork/evaluator/NativeFunctionProvider.kt create mode 100644 examples/ffi.pork create mode 100644 examples/loop.pork create mode 100644 ffi/build.gradle.kts create mode 100644 ffi/src/main/kotlin/gay/pizza/pork/ffi/JnaNativeProvider.kt diff --git a/ast/src/main/ast/pork.yml b/ast/src/main/ast/pork.yml index 9f938b6..6af5e72 100644 --- a/ast/src/main/ast/pork.yml +++ b/ast/src/main/ast/pork.yml @@ -95,16 +95,18 @@ types: - name: arguments type: List - name: block - type: Block + type: Block? + - name: native + type: Native? If: parent: Expression values: - name: condition type: Expression - - name: thenExpression - type: Expression - - name: elseExpression - type: Expression? + - name: thenBlock + type: Block + - name: elseBlock + type: Block? ImportDeclaration: parent: Declaration values: @@ -150,3 +152,23 @@ types: values: - name: 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 diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/Break.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/Break.kt new file mode 100644 index 0000000..7a61f95 --- /dev/null +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/Break.kt @@ -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 visit(visitor: NodeVisitor): 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() +} diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/Continue.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/Continue.kt new file mode 100644 index 0000000..fca1a2c --- /dev/null +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/Continue.kt @@ -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 visit(visitor: NodeVisitor): 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() +} diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/FunctionDefinition.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/FunctionDefinition.kt index 3a337fd..bbc3877 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/FunctionDefinition.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/FunctionDefinition.kt @@ -6,18 +6,18 @@ import kotlinx.serialization.Serializable @Serializable @SerialName("functionDefinition") -class FunctionDefinition(override val modifiers: DefinitionModifiers, override val symbol: Symbol, val arguments: List, val block: Block) : Definition() { +class FunctionDefinition(override val modifiers: DefinitionModifiers, override val symbol: Symbol, val arguments: List, val block: Block?, val native: Native?) : Definition() { override val type: NodeType = NodeType.FunctionDefinition override fun visitChildren(visitor: NodeVisitor): List = - visitor.visitAll(listOf(symbol), arguments, listOf(block)) + visitor.visitAll(listOf(symbol), arguments, listOf(block), listOf(native)) override fun visit(visitor: NodeVisitor): T = visitor.visitFunctionDefinition(this) override fun equals(other: Any?): Boolean { if (other !is FunctionDefinition) return false - return other.modifiers == modifiers && other.symbol == symbol && other.arguments == arguments && other.block == block + return other.modifiers == modifiers && other.symbol == symbol && other.arguments == arguments && other.block == block && other.native == native } override fun hashCode(): Int { @@ -25,6 +25,7 @@ class FunctionDefinition(override val modifiers: DefinitionModifiers, override v result = 31 * result + symbol.hashCode() result = 31 * result + arguments.hashCode() result = 31 * result + block.hashCode() + result = 31 * result + native.hashCode() result = 31 * result + type.hashCode() return result } diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/If.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/If.kt index f13cbc9..fe4dd35 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/If.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/If.kt @@ -6,24 +6,24 @@ import kotlinx.serialization.Serializable @Serializable @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 fun visitChildren(visitor: NodeVisitor): List = - visitor.visitNodes(condition, thenExpression, elseExpression) + visitor.visitNodes(condition, thenBlock, elseBlock) override fun visit(visitor: NodeVisitor): T = visitor.visitIf(this) override fun equals(other: Any?): Boolean { if (other !is If) return false - return other.condition == condition && other.thenExpression == thenExpression && other.elseExpression == elseExpression + return other.condition == condition && other.thenBlock == thenBlock && other.elseBlock == elseBlock } override fun hashCode(): Int { var result = condition.hashCode() - result = 31 * result + thenExpression.hashCode() - result = 31 * result + elseExpression.hashCode() + result = 31 * result + thenBlock.hashCode() + result = 31 * result + elseBlock.hashCode() result = 31 * result + type.hashCode() return result } diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/Native.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/Native.kt new file mode 100644 index 0000000..3e56ed2 --- /dev/null +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/Native.kt @@ -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 visitChildren(visitor: NodeVisitor): List = + visitor.visitNodes(form, definition) + + override fun visit(visitor: NodeVisitor): 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 + } +} diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/NodeCoalescer.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/NodeCoalescer.kt index 4fdb1b8..9be5ca7 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/NodeCoalescer.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/NodeCoalescer.kt @@ -8,9 +8,15 @@ class NodeCoalescer(val handler: (Node) -> Unit) : NodeVisitor { override fun visitBooleanLiteral(node: BooleanLiteral): Unit = handle(node) + override fun visitBreak(node: Break): Unit = + handle(node) + override fun visitCompilationUnit(node: CompilationUnit): Unit = handle(node) + override fun visitContinue(node: Continue): Unit = + handle(node) + override fun visitFunctionCall(node: FunctionCall): Unit = handle(node) @@ -35,6 +41,9 @@ class NodeCoalescer(val handler: (Node) -> Unit) : NodeVisitor { override fun visitListLiteral(node: ListLiteral): Unit = handle(node) + override fun visitNative(node: Native): Unit = + handle(node) + override fun visitParentheses(node: Parentheses): Unit = handle(node) @@ -50,6 +59,9 @@ class NodeCoalescer(val handler: (Node) -> Unit) : NodeVisitor { override fun visitSymbolReference(node: SymbolReference): Unit = handle(node) + override fun visitWhile(node: While): Unit = + handle(node) + fun handle(node: Node) { handler(node) node.visitChildren(this) diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt index 43342e7..68bc562 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt @@ -6,7 +6,9 @@ enum class NodeType(val parent: NodeType? = null) { Block(Node), Expression(Node), BooleanLiteral(Expression), + Break(Expression), CompilationUnit(Node), + Continue(Expression), Declaration(Node), Definition(Node), FunctionCall(Expression), @@ -17,9 +19,11 @@ enum class NodeType(val parent: NodeType? = null) { IntLiteral(Expression), LetAssignment(Expression), ListLiteral(Expression), + Native(Node), Parentheses(Expression), PrefixOperation(Expression), StringLiteral(Expression), Symbol(Node), - SymbolReference(Expression) + SymbolReference(Expression), + While(Expression) } diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/NodeVisitor.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/NodeVisitor.kt index 24f5b10..b6e22ac 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/NodeVisitor.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/NodeVisitor.kt @@ -6,8 +6,12 @@ interface NodeVisitor { fun visitBooleanLiteral(node: BooleanLiteral): T + fun visitBreak(node: Break): T + fun visitCompilationUnit(node: CompilationUnit): T + fun visitContinue(node: Continue): T + fun visitFunctionCall(node: FunctionCall): T fun visitFunctionDefinition(node: FunctionDefinition): T @@ -24,6 +28,8 @@ interface NodeVisitor { fun visitListLiteral(node: ListLiteral): T + fun visitNative(node: Native): T + fun visitParentheses(node: Parentheses): T fun visitPrefixOperation(node: PrefixOperation): T @@ -33,4 +39,6 @@ interface NodeVisitor { fun visitSymbol(node: Symbol): T fun visitSymbolReference(node: SymbolReference): T + + fun visitWhile(node: While): T } diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/NodeVisitorExtensions.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/NodeVisitorExtensions.kt index 31146f1..fb4f404 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/NodeVisitorExtensions.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/NodeVisitorExtensions.kt @@ -19,10 +19,14 @@ fun NodeVisitor.visit(node: Node): T = is PrefixOperation -> visitPrefixOperation(node) is StringLiteral -> visitStringLiteral(node) is SymbolReference -> visitSymbolReference(node) + is While -> visitWhile(node) + is Break -> visitBreak(node) + is Continue -> visitContinue(node) + is Native -> visitNative(node) } fun NodeVisitor.visitNodes(vararg nodes: Node?): List = nodes.asSequence().filterNotNull().map { visit(it) }.toList() -fun NodeVisitor.visitAll(vararg nodeLists: List): List = - nodeLists.asSequence().flatten().map { visit(it) }.toList() +fun NodeVisitor.visitAll(vararg nodeLists: List): List = + nodeLists.asSequence().flatten().filterNotNull().map { visit(it) }.toList() diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/While.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/While.kt new file mode 100644 index 0000000..81fb8fd --- /dev/null +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/While.kt @@ -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 visitChildren(visitor: NodeVisitor): List = + visitor.visitNodes(condition, block) + + override fun visit(visitor: NodeVisitor): 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 + } +} diff --git a/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstCodegen.kt b/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstCodegen.kt index 89a9dfb..81ec30a 100644 --- a/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstCodegen.kt +++ b/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstCodegen.kt @@ -120,11 +120,12 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld extensionOf = "NodeVisitor", returnType = "List", parameters = mutableListOf( - KotlinParameter("nodeLists", type = "List", vararg = true) + KotlinParameter("nodeLists", type = "List", vararg = 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) write("NodeVisitorExtensions.kt", KotlinWriter(visitorExtensionSet)) @@ -239,16 +240,18 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld kotlinClassLike.inherits.add("$parentName()") } - for (value in type.values) { - val member = KotlinMember(value.name, toKotlinType(value.typeRef)) - member.abstract = value.abstract - if (type.isParentAbstract(value)) { - member.overridden = true + if (type.values != null) { + for (value in type.values!!) { + val member = KotlinMember(value.name, toKotlinType(value.typeRef)) + member.abstract = value.abstract + if (type.isParentAbstract(value)) { + member.overridden = true + } + if (role == AstTypeRole.ValueHolder) { + member.mutable = true + } + kotlinClassLike.members.add(member) } - if (role == AstTypeRole.ValueHolder) { - member.mutable = true - } - kotlinClassLike.members.add(member) } if (role == AstTypeRole.Enum) { @@ -279,25 +282,26 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld ), 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 if (anyListMembers) { - val visitParameters = type.values.mapNotNull { + val visitParameters = (type.values?.mapNotNull { if (it.typeRef.primitive != null) { null } else if (it.typeRef.type != null && !world.typeRegistry.roleOfType(it.typeRef.type).isNodeInherited()) { null - } else if (it.typeRef.form == AstTypeRefForm.Single) { + } else if (it.typeRef.form == AstTypeRefForm.Single || + it.typeRef.form == AstTypeRefForm.Nullable) { "listOf(${it.name})" } else { it.name } - }.joinToString(", ") + } ?: emptyList()).joinToString(", ") elideVisitChildren = visitParameters.isEmpty() visitChildrenFunction.body.add("visitor.visitAll(${visitParameters})") } else { - val visitParameters = type.values.mapNotNull { + val visitParameters = (type.values?.mapNotNull { if (it.typeRef.primitive != null) { null } else if (it.typeRef.type != null && @@ -306,7 +310,7 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld } else { it.name } - }.joinToString(", ") + } ?: emptyList()).joinToString(", ") elideVisitChildren = visitParameters.isEmpty() visitChildrenFunction.body.add("visitor.visitNodes(${visitParameters})") } @@ -341,9 +345,12 @@ class AstCodegen(val pkg: String, val outputDirectory: Path, val world: AstWorld "Any?" )) 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" }.joinToString(" && ") + if (predicate.isEmpty()) { + predicate = "true" + } equalsFunction.body.add("return $predicate") kotlinClassLike.functions.add(equalsFunction) diff --git a/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstType.kt b/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstType.kt index 1af6831..53ddc24 100644 --- a/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstType.kt +++ b/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstType.kt @@ -1,17 +1,24 @@ package gay.pizza.pork.buildext.ast class AstType(val name: String, var parent: AstType? = null) { - private val internalValues = mutableListOf() + private var internalValues: MutableList? = null private val internalEnums = mutableListOf() - val values: List + val values: List? get() = internalValues val enums: List get() = internalEnums + internal fun markHasValues() { + if (internalValues == null) { + internalValues = mutableListOf() + } + } + internal fun addValue(value: AstValue) { - internalValues.add(value) + markHasValues() + internalValues!!.add(value) } internal fun addEnum(enum: AstEnum) { @@ -25,7 +32,7 @@ class AstType(val name: String, var parent: AstType? = null) { var current = parent while (current != null) { - val abstract = current.values.firstOrNull { + val abstract = current.values?.firstOrNull { it.name == value.name && it.abstract } if (abstract != null) { diff --git a/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstTypeDescription.kt b/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstTypeDescription.kt index 62e9573..e2a0b53 100644 --- a/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstTypeDescription.kt +++ b/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstTypeDescription.kt @@ -2,6 +2,6 @@ package gay.pizza.pork.buildext.ast data class AstTypeDescription( val parent: String? = null, - val values: List = emptyList(), + val values: List? = null, val enums: List = emptyList() ) diff --git a/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstTypeRegistry.kt b/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstTypeRegistry.kt index ea96776..0e7a8e5 100644 --- a/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstTypeRegistry.kt +++ b/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstTypeRegistry.kt @@ -21,13 +21,14 @@ class AstTypeRegistry { when { type.enums.isNotEmpty() -> AstTypeRole.Enum - type.parent == null && type.values.isEmpty() -> + type.parent == null && type.values == null -> 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 - type.parent != null && type.values.none { it.abstract } -> + type.parent != null && (type.values != null && type.values!!.none { it.abstract }) -> AstTypeRole.AstNode - type.parent == null && type.values.isNotEmpty() -> + type.parent == null && (type.values != null && type.values!!.isNotEmpty()) -> AstTypeRole.ValueHolder else -> throw RuntimeException("Unable to determine role of type ${type.name}") } diff --git a/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstWorld.kt b/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstWorld.kt index c656506..6dce257 100644 --- a/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstWorld.kt +++ b/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstWorld.kt @@ -39,10 +39,13 @@ class AstWorld { type.parent = world.typeRegistry.lookup(typeDescription.parent) } - for (value in typeDescription.values) { - val typeRef = AstTypeRef.parse(value.type, world.typeRegistry) - val typeValue = AstValue(value.name, typeRef, abstract = value.required) - type.addValue(typeValue) + if (typeDescription.values != null) { + type.markHasValues() + for (value in typeDescription.values) { + 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) { diff --git a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/CompilationUnitContext.kt b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/CompilationUnitContext.kt index 31a73b6..669df6b 100644 --- a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/CompilationUnitContext.kt +++ b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/CompilationUnitContext.kt @@ -39,7 +39,7 @@ class CompilationUnitContext( } private fun definitionValue(definition: Definition): Any = when (definition) { - is FunctionDefinition -> FunctionContext(definition, internalScope) + is FunctionDefinition -> FunctionContext(this, definition) } private fun processAllImports() { diff --git a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/EvaluationVisitor.kt b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/EvaluationVisitor.kt index 0a06df7..f0f2a6e 100644 --- a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/EvaluationVisitor.kt +++ b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/EvaluationVisitor.kt @@ -8,6 +8,9 @@ class EvaluationVisitor(root: Scope) : NodeVisitor { override fun visitIntLiteral(node: IntLiteral): Any = node.value override fun visitStringLiteral(node: StringLiteral): Any = node.text override fun visitBooleanLiteral(node: BooleanLiteral): Any = node.value + + override fun visitBreak(node: Break): Any = throw BreakMarker + override fun visitListLiteral(node: ListLiteral): Any = node.items.map { it.visit(this) } @@ -15,7 +18,8 @@ class EvaluationVisitor(root: Scope) : NodeVisitor { override fun visitFunctionCall(node: FunctionCall): Any { 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 { @@ -27,6 +31,26 @@ class EvaluationVisitor(root: Scope) : NodeVisitor { override fun visitSymbolReference(node: SymbolReference): Any = 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 = node.expression.visit(this) @@ -45,11 +69,12 @@ class EvaluationVisitor(root: Scope) : NodeVisitor { override fun visitIf(node: If): Any { val condition = node.condition.visit(this) return if (condition == true) { - node.thenExpression.visit(this) - } else { - val elseExpression = node.elseExpression - elseExpression?.visit(this) ?: None - } + val blockFunction = node.thenBlock.visit(this) as BlockFunction + scoped { blockFunction.call() } + } else if (node.elseBlock != null) { + val blockFunction = node.elseBlock!!.visit(this) as BlockFunction + scoped { blockFunction.call() } + } else None } override fun visitInfixOperation(node: InfixOperation): Any { @@ -93,21 +118,42 @@ class EvaluationVisitor(root: Scope) : NodeVisitor { override fun visitFunctionDefinition(node: FunctionDefinition): Any { throw RuntimeException( "Function declarations cannot be visited in an EvaluationVisitor. " + - "Utilize a FunctionContext." + "Utilize a FunctionContext." ) } override fun visitImportDeclaration(node: ImportDeclaration): Any { throw RuntimeException( "Import declarations cannot be visited in an EvaluationVisitor. " + - "Utilize an EvaluationContext." + "Utilize an CompilationUnitContext." ) } override fun visitCompilationUnit(node: CompilationUnit): Any { throw RuntimeException( "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 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") } diff --git a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/Evaluator.kt b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/Evaluator.kt index 924e87a..a9eb203 100644 --- a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/Evaluator.kt +++ b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/Evaluator.kt @@ -4,6 +4,7 @@ import gay.pizza.pork.frontend.World class Evaluator(val world: World, val scope: Scope) { private val contexts = mutableMapOf() + private val nativeFunctionProviders = mutableMapOf() fun evaluate(path: String): Scope = context(path).externalScope @@ -17,4 +18,13 @@ class Evaluator(val world: World, val scope: Scope) { context.initIfNeeded() 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 + } } diff --git a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/FunctionContext.kt b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/FunctionContext.kt index 09f4653..9584c1c 100644 --- a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/FunctionContext.kt +++ b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/FunctionContext.kt @@ -2,14 +2,27 @@ package gay.pizza.pork.evaluator 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 { - val scope = internalScope.fork() + val scope = compilationUnitContext.internalScope.fork() for ((index, argumentSymbol) in node.arguments.withIndex()) { 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 blockFunction = visitor.visitBlock(node.block) + val blockFunction = visitor.visitBlock(node.block!!) return blockFunction.call() } } diff --git a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/NativeFunctionProvider.kt b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/NativeFunctionProvider.kt new file mode 100644 index 0000000..4c385fb --- /dev/null +++ b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/NativeFunctionProvider.kt @@ -0,0 +1,5 @@ +package gay.pizza.pork.evaluator + +interface NativeFunctionProvider { + fun provideNativeFunction(definition: String): CallableFunction +} diff --git a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/Scope.kt b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/Scope.kt index eedcff2..5ed68ac 100644 --- a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/Scope.kt +++ b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/Scope.kt @@ -41,27 +41,19 @@ class Scope(val parent: Scope? = null, inherits: List = emptyList()) { 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 = Scope(this) - fun leave(): Scope { - if (parent == null) { - throw RuntimeException("Parent context not found") - } - return parent - } - internal fun inherit(scope: Scope) { inherited.add(scope) } + fun leave(): Scope { + if (parent == null) { + throw RuntimeException("Attempted to leave the root scope!") + } + return parent + } + private object NotFound } diff --git a/examples/ffi.pork b/examples/ffi.pork new file mode 100644 index 0000000..6a51352 --- /dev/null +++ b/examples/ffi.pork @@ -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) + } +} diff --git a/examples/fib.pork b/examples/fib.pork index c1dbc98..032793f 100644 --- a/examples/fib.pork +++ b/examples/fib.pork @@ -1,10 +1,14 @@ /* fibonacci sequence */ func fib(n) { - if n == 0 - then 0 - else if n == 1 - then 1 - else fib(n - 1) + fib(n - 2) + if n == 0 { + 0 + } else { + if n == 1 { + 1 + } else { + fib(n - 1) + fib(n - 2) + } + } } export func main() { diff --git a/examples/loop.pork b/examples/loop.pork new file mode 100644 index 0000000..18555d0 --- /dev/null +++ b/examples/loop.pork @@ -0,0 +1,6 @@ +export func main() { + while true { + println("Hello World") + break + } +} diff --git a/ffi/build.gradle.kts b/ffi/build.gradle.kts new file mode 100644 index 0000000..3e5b11b --- /dev/null +++ b/ffi/build.gradle.kts @@ -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") +} diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JnaNativeProvider.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JnaNativeProvider.kt new file mode 100644 index 0000000..f42cc3d --- /dev/null +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JnaNativeProvider.kt @@ -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, 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") + } +} diff --git a/parser/src/main/kotlin/gay/pizza/pork/parser/Parser.kt b/parser/src/main/kotlin/gay/pizza/pork/parser/Parser.kt index 0238208..eebc629 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/Parser.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/Parser.kt @@ -72,13 +72,26 @@ class Parser(source: PeekableSource, val attribution: NodeAttribution) { private fun readIf(): If = within { expect(TokenType.If) val condition = readExpression() - expect(TokenType.Then) - val thenExpression = readExpression() - var elseExpression: Expression? = null + val thenBlock = readBlock() + var elseBlock: Block? = null 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 { @@ -120,6 +133,20 @@ class Parser(source: PeekableSource, val attribution: NodeAttribution) { readIf() } + TokenType.While -> { + readWhile() + } + + TokenType.Break -> { + expect(TokenType.Break) + Break() + } + + TokenType.Continue -> { + expect(TokenType.Continue) + Continue() + } + else -> { throw RuntimeException( "Failed to parse token: ${token.type} '${token.text}' as" + @@ -178,7 +205,15 @@ class Parser(source: PeekableSource, val attribution: NodeAttribution) { readSymbolRaw() } 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? { diff --git a/parser/src/main/kotlin/gay/pizza/pork/parser/Printer.kt b/parser/src/main/kotlin/gay/pizza/pork/parser/Printer.kt index b5e679a..4ae9d95 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/Printer.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/Printer.kt @@ -38,6 +38,8 @@ class Printer(buffer: StringBuilder) : NodeVisitor { } } + override fun visitBreak(node: Break): Unit = append("break") + override fun visitListLiteral(node: ListLiteral) { append("[") if (node.items.isNotEmpty()) { @@ -55,6 +57,13 @@ class Printer(buffer: StringBuilder) : NodeVisitor { append("]") } + override fun visitNative(node: Native) { + append("native ") + visit(node.form) + append(" ") + visit(node.definition) + } + override fun visitSymbol(node: Symbol) { append(node.id) } @@ -82,6 +91,13 @@ class Printer(buffer: StringBuilder) : NodeVisitor { visit(node.symbol) } + override fun visitWhile(node: While) { + append("while ") + visit(node.condition) + append(" ") + visit(node.block) + } + override fun visitParentheses(node: Parentheses) { append("(") visit(node.expression) @@ -96,18 +112,13 @@ class Printer(buffer: StringBuilder) : NodeVisitor { override fun visitIf(node: If) { append("if ") visit(node.condition) - append(" then") - out.increaseIndent() - appendLine() - visit(node.thenExpression) - out.decreaseIndent() - if (node.elseExpression != null) { - appendLine() + append(" ") + visit(node.thenBlock) + if (node.elseBlock != null) { + append(" ") append("else") - out.increaseIndent() - appendLine() - visit(node.elseExpression!!) - out.decreaseIndent() + append(" ") + visit(node.elseBlock!!) } } @@ -120,7 +131,7 @@ class Printer(buffer: StringBuilder) : NodeVisitor { } override fun visitFunctionDefinition(node: FunctionDefinition) { - append("fn ") + append("func ") visit(node.symbol) append("(") for ((index, argument) in node.arguments.withIndex()) { @@ -130,7 +141,13 @@ class Printer(buffer: StringBuilder) : NodeVisitor { } } append(") ") - visit(node.block) + if (node.block != null) { + visit(node.block!!) + } + + if (node.native != null) { + visit(node.native!!) + } } override fun visitBlock(node: Block) { @@ -167,4 +184,6 @@ class Printer(buffer: StringBuilder) : NodeVisitor { appendLine() } } + + override fun visitContinue(node: Continue): Unit = append("continue") } diff --git a/parser/src/main/kotlin/gay/pizza/pork/parser/TokenType.kt b/parser/src/main/kotlin/gay/pizza/pork/parser/TokenType.kt index 242cef6..76191b1 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/TokenType.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/TokenType.kt @@ -25,11 +25,14 @@ enum class TokenType(vararg properties: TokenTypeProperty) { False(Keyword("false"), KeywordFamily), True(Keyword("true"), KeywordFamily), If(Keyword("if"), KeywordFamily), - Then(Keyword("then"), KeywordFamily), Else(Keyword("else"), KeywordFamily), + While(Keyword("while"), KeywordFamily), + Continue(Keyword("continue"), KeywordFamily), + Break(Keyword("break"), KeywordFamily), Import(Keyword("import"), KeywordFamily), Export(Keyword("export"), KeywordFamily), Func(Keyword("func"), KeywordFamily), + Native(Keyword("native"), KeywordFamily), Let(Keyword("let"), KeywordFamily), Whitespace(CharConsumer { it == ' ' || it == '\r' || it == '\n' || it == '\t' }), BlockComment(CommentFamily), diff --git a/settings.gradle.kts b/settings.gradle.kts index 138867e..9599a34 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -8,16 +8,6 @@ include( ":parser", ":frontend", ":evaluator", + ":ffi", ":tool" ) - -dependencyResolutionManagement { - versionCatalogs { - create("libs") { - version("clikt", "4.2.0") - - library("clikt", "com.github.ajalt.clikt", "clikt") - .versionRef("clikt") - } - } -} diff --git a/tool/build.gradle.kts b/tool/build.gradle.kts index 01edd4a..9d2d44c 100644 --- a/tool/build.gradle.kts +++ b/tool/build.gradle.kts @@ -10,7 +10,8 @@ dependencies { api(project(":parser")) api(project(":frontend")) api(project(":evaluator")) - implementation(libs.clikt) + api(project(":ffi")) + api("com.github.ajalt.clikt:clikt:4.2.0") implementation(project(":common")) } diff --git a/tool/src/main/kotlin/gay/pizza/pork/tool/RunCommand.kt b/tool/src/main/kotlin/gay/pizza/pork/tool/RunCommand.kt index d097930..b516cd0 100644 --- a/tool/src/main/kotlin/gay/pizza/pork/tool/RunCommand.kt +++ b/tool/src/main/kotlin/gay/pizza/pork/tool/RunCommand.kt @@ -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.option import gay.pizza.dough.fs.PlatformFsProvider +import gay.pizza.pork.evaluator.Arguments import gay.pizza.pork.evaluator.CallableFunction import gay.pizza.pork.evaluator.None import gay.pizza.pork.evaluator.Scope +import gay.pizza.pork.ffi.JnaNativeProvider class RunCommand : CliktCommand(help = "Run Program", name = "run") { val loop by option("--loop", help = "Loop Program").flag() @@ -28,8 +30,12 @@ class RunCommand : CliktCommand(help = "Run Program", name = "run") { None }) + val main = tool.loadMainFunction(scope, setupEvaluator = { + addNativeFunctionProvider("ffi", JnaNativeProvider()) + }) + maybeLoopAndMeasure(loop, measure) { - tool.evaluate(scope) + main.call(Arguments(emptyList())) } } } diff --git a/tool/src/main/kotlin/gay/pizza/pork/tool/Tool.kt b/tool/src/main/kotlin/gay/pizza/pork/tool/Tool.kt index 6a2a47e..d086382 100644 --- a/tool/src/main/kotlin/gay/pizza/pork/tool/Tool.kt +++ b/tool/src/main/kotlin/gay/pizza/pork/tool/Tool.kt @@ -5,6 +5,7 @@ import gay.pizza.pork.parser.Printer import gay.pizza.pork.ast.CompilationUnit import gay.pizza.pork.ast.visit import gay.pizza.pork.evaluator.Arguments +import gay.pizza.pork.evaluator.CallableFunction import gay.pizza.pork.evaluator.Evaluator import gay.pizza.pork.evaluator.Scope import gay.pizza.pork.frontend.ContentSource @@ -29,11 +30,12 @@ abstract class Tool { fun visit(visitor: NodeVisitor): T = visitor.visit(parse()) - fun evaluate(scope: Scope) { + fun loadMainFunction(scope: Scope, setupEvaluator: Evaluator.() -> Unit = {}): CallableFunction { val contentSource = createContentSource() val world = World(contentSource) val evaluator = Evaluator(world, scope) + setupEvaluator(evaluator) val resultingScope = evaluator.evaluate(rootFilePath()) - resultingScope.call("main", Arguments(emptyList())) + return resultingScope.value("main") as CallableFunction } }