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
type: List<Symbol>
- 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

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
@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 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 =
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
}

View File

@ -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 <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 =
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
}

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 =
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<Unit> {
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<Unit> {
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)

View File

@ -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)
}

View File

@ -6,8 +6,12 @@ interface NodeVisitor<T> {
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<T> {
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<T> {
fun visitSymbol(node: Symbol): 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 StringLiteral -> visitStringLiteral(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> =
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()
fun <T> NodeVisitor<T>.visitAll(vararg nodeLists: List<Node?>): List<T> =
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>",
returnType = "List<T>",
parameters = mutableListOf(
KotlinParameter("nodeLists", type = "List<Node>", vararg = true)
KotlinParameter("nodeLists", type = "List<Node?>", 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)

View File

@ -1,17 +1,24 @@
package gay.pizza.pork.buildext.ast
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>()
val values: List<AstValue>
val values: List<AstValue>?
get() = internalValues
val enums: List<AstEnum>
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) {

View File

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

View File

@ -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}")
}

View File

@ -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) {

View File

@ -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() {

View File

@ -8,6 +8,9 @@ class EvaluationVisitor(root: Scope) : NodeVisitor<Any> {
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<Any> {
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<Any> {
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<Any> {
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<Any> {
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 <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) {
private val contexts = mutableMapOf<String, CompilationUnitContext>()
private val nativeFunctionProviders = mutableMapOf<String, NativeFunctionProvider>()
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
}
}

View File

@ -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()
}
}

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
}
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
}

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 */
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() {

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 {
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<Token>, 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<Token>, 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? {

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) {
append("[")
if (node.items.isNotEmpty()) {
@ -55,6 +57,13 @@ class Printer(buffer: StringBuilder) : NodeVisitor<Unit> {
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<Unit> {
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<Unit> {
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<Unit> {
}
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<Unit> {
}
}
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<Unit> {
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),
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),

View File

@ -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")
}
}
}

View File

@ -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"))
}

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.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()))
}
}
}

View File

@ -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 <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 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
}
}