Skip to content

Commit

Permalink
Exportable functions, common frontend, and scoped imports.
Browse files Browse the repository at this point in the history
  • Loading branch information
azenla committed Sep 3, 2023
1 parent 7a77198 commit 4f567eb
Show file tree
Hide file tree
Showing 20 changed files with 336 additions and 163 deletions.
2 changes: 1 addition & 1 deletion examples/fib.pork
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ func fib(n) {
else fib(n - 1) + fib(n - 2)
}

func main() {
export func main() {
result = fib(20)
println(result)
}
2 changes: 1 addition & 1 deletion examples/imports.pork
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import "module.pork"

func main() {
export func main() {
hello()
}
2 changes: 1 addition & 1 deletion examples/module.pork
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
func hello() {
export func hello() {
println("Hello World")
}
2 changes: 1 addition & 1 deletion examples/syntax.pork
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
func main() {
export func main() {
three = 3
two = 2

Expand Down
5 changes: 4 additions & 1 deletion src/main/kotlin/gay/pizza/pork/ast/nodes/Definition.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@ package gay.pizza.pork.ast.nodes
import kotlinx.serialization.Serializable

@Serializable
sealed class Definition : Node()
sealed class Definition : Node() {
abstract val symbol: Symbol
abstract val modifiers: DefinitionModifiers
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package gay.pizza.pork.ast.nodes

import kotlinx.serialization.Serializable

@Serializable
class DefinitionModifiers(
var export: Boolean
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import kotlinx.serialization.Serializable

@Serializable
@SerialName("functionDefinition")
class FunctionDefinition(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
) : Definition() {
override val type: NodeType = NodeType.FunctionDeclaration

override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
Expand Down
6 changes: 4 additions & 2 deletions src/main/kotlin/gay/pizza/pork/cli/FileTool.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package gay.pizza.pork.cli

import gay.pizza.pork.frontend.ContentSource
import gay.pizza.pork.frontend.FsContentSource
import gay.pizza.pork.parse.CharSource
import gay.pizza.pork.parse.StringCharSource
import java.nio.file.Path
import kotlin.io.path.readText

class FileTool(val path: Path) : Tool() {
override fun createCharSource(): CharSource = StringCharSource(path.readText())
override fun resolveImportSource(path: String): CharSource =
StringCharSource(this.path.parent.resolve(path).readText())
override fun createContentSource(): ContentSource = FsContentSource(path.parent)
override fun rootFilePath(): String = path.fileName.toString()
}
2 changes: 0 additions & 2 deletions src/main/kotlin/gay/pizza/pork/cli/RunCommand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package gay.pizza.pork.cli
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.path
import gay.pizza.pork.eval.Arguments
import gay.pizza.pork.eval.CallableFunction
import gay.pizza.pork.eval.Scope

Expand All @@ -19,6 +18,5 @@ class RunCommand : CliktCommand(help = "Run Program", name = "run") {
}
})
tool.evaluate(scope)
scope.call("main", Arguments(emptyList()))
}
}
23 changes: 11 additions & 12 deletions src/main/kotlin/gay/pizza/pork/cli/Tool.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ package gay.pizza.pork.cli
import gay.pizza.pork.ast.NodeVisitor
import gay.pizza.pork.ast.Printer
import gay.pizza.pork.ast.nodes.CompilationUnit
import gay.pizza.pork.eval.Evaluator
import gay.pizza.pork.eval.ImportLoader
import gay.pizza.pork.eval.Scope
import gay.pizza.pork.eval.*
import gay.pizza.pork.frontend.ContentSource
import gay.pizza.pork.frontend.World
import gay.pizza.pork.parse.*

abstract class Tool {
abstract fun createCharSource(): CharSource
abstract fun resolveImportSource(path: String): CharSource
abstract fun createContentSource(): ContentSource
abstract fun rootFilePath(): String

fun tokenize(): TokenStream =
Tokenizer(createCharSource()).tokenize()
Expand All @@ -21,17 +22,15 @@ abstract class Tool {
fun highlight(scheme: HighlightScheme): List<Highlight> =
Highlighter(scheme).highlight(tokenize())

fun evaluate(scope: Scope = Scope()): Any =
visit(Evaluator(scope, FrontendImportLoader(this)))

fun reprint(): String = buildString { visit(Printer(this)) }

fun <T> visit(visitor: NodeVisitor<T>): T = visitor.visit(parse())

private class FrontendImportLoader(val frontend: Tool) : ImportLoader {
override fun load(path: String): CompilationUnit {
val tokenStream = Tokenizer(frontend.resolveImportSource(path)).tokenize()
return Parser(TokenStreamSource(tokenStream), DiscardNodeAttribution).readCompilationUnit()
}
fun evaluate(scope: Scope) {
val contentSource = createContentSource()
val world = World(contentSource)
val evaluator = Evaluator(world, scope)
val resultingScope = evaluator.evaluate(rootFilePath())
resultingScope.call("main", Arguments(emptyList()))
}
}
30 changes: 30 additions & 0 deletions src/main/kotlin/gay/pizza/pork/eval/EvaluationContext.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package gay.pizza.pork.eval

import gay.pizza.pork.ast.nodes.CompilationUnit
import gay.pizza.pork.ast.nodes.ImportDeclaration

class EvaluationContext(
val compilationUnit: CompilationUnit,
val evaluationContextProvider: EvaluationContextProvider,
rootScope: Scope
) {
val internalScope = rootScope.fork()
val externalScope = rootScope.fork()

private val evaluationVisitor = EvaluationVisitor(internalScope)

fun setup() {
val imports = compilationUnit.declarations.filterIsInstance<ImportDeclaration>()
for (import in imports) {
val evaluationContext = evaluationContextProvider.provideEvaluationContext(import.path.text)
internalScope.inherit(evaluationContext.externalScope)
}

for (definition in compilationUnit.definitions) {
evaluationVisitor.visit(definition)
if (definition.modifiers.export) {
externalScope.define(definition.symbol.id, internalScope.value(definition.symbol.id))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package gay.pizza.pork.eval

interface EvaluationContextProvider {
fun provideEvaluationContext(path: String): EvaluationContext
}
143 changes: 143 additions & 0 deletions src/main/kotlin/gay/pizza/pork/eval/EvaluationVisitor.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package gay.pizza.pork.eval

import gay.pizza.pork.ast.NodeVisitor
import gay.pizza.pork.ast.nodes.*

class EvaluationVisitor(val root: Scope) : NodeVisitor<Any> {
private var currentScope: Scope = root

override fun visitIntLiteral(node: IntLiteral): Any = node.value
override fun visitStringLiteral(node: StringLiteral): Any = node.text
override fun visitBooleanLiteral(node: BooleanLiteral): Any = node.value
override fun visitListLiteral(node: ListLiteral): Any = node.items.map { visit(it) }

override fun visitSymbol(node: Symbol): Any = None

override fun visitFunctionCall(node: FunctionCall): Any {
val arguments = node.arguments.map { visit(it) }
return currentScope.call(node.symbol.id, Arguments(arguments))
}

override fun visitDefine(node: Assignment): Any {
val value = visit(node.value)
currentScope.define(node.symbol.id, value)
return value
}

override fun visitSymbolReference(node: SymbolReference): Any =
currentScope.value(node.symbol.id)

override fun visitLambda(node: Lambda): CallableFunction {
return CallableFunction { arguments ->
currentScope = currentScope.fork()
for ((index, argumentSymbol) in node.arguments.withIndex()) {
currentScope.define(argumentSymbol.id, arguments.values[index])
}
try {
var value: Any? = null
for (expression in node.expressions) {
value = visit(expression)
}
value ?: None
} finally {
currentScope = currentScope.leave()
}
}
}

override fun visitParentheses(node: Parentheses): Any = visit(node.expression)

override fun visitPrefixOperation(node: PrefixOperation): Any {
val value = visit(node.expression)
return when (node.op) {
PrefixOperator.Negate -> {
if (value !is Boolean) {
throw RuntimeException("Cannot negate a value which is not a boolean.")
}
!value
}
}
}

override fun visitIf(node: If): Any {
val condition = visit(node.condition)
return if (condition == true) {
visit(node.thenExpression)
} else {
if (node.elseExpression != null) {
visit(node.elseExpression)
} else {
None
}
}
}

override fun visitInfixOperation(node: InfixOperation): Any {
val left = visit(node.left)
val right = visit(node.right)

when (node.op) {
InfixOperator.Equals -> {
return left == right
}
InfixOperator.NotEquals -> {
return left != right
}
else -> {}
}

if (left !is Number || right !is Number) {
throw RuntimeException("Failed to evaluate infix operation, bad types.")
}

val leftInt = left.toInt()
val rightInt = right.toInt()

return when (node.op) {
InfixOperator.Plus -> leftInt + rightInt
InfixOperator.Minus -> leftInt - rightInt
InfixOperator.Multiply -> leftInt * rightInt
InfixOperator.Divide -> leftInt / rightInt
else -> throw RuntimeException("Unable to handle operation ${node.op}")
}
}

override fun visitFunctionDeclaration(node: FunctionDefinition): Any {
val blockFunction = visitBlock(node.block) as BlockFunction
val function = CallableFunction { arguments ->
currentScope = currentScope.fork()
for ((index, argumentSymbol) in node.arguments.withIndex()) {
currentScope.define(argumentSymbol.id, arguments.values[index])
}
try {
return@CallableFunction blockFunction.call()
} finally {
currentScope = currentScope.leave()
}
}
currentScope.define(node.symbol.id, function)
return None
}

override fun visitBlock(node: Block): Any = BlockFunction {
var value: Any? = null
for (expression in node.expressions) {
value = visit(expression)
}
value ?: None
}

override fun visitImportDeclaration(node: ImportDeclaration): Any {
throw RuntimeException(
"Import declarations cannot be visited in an EvaluationVisitor. " +
"Utilize an EvaluationContext."
)
}

override fun visitCompilationUnit(node: CompilationUnit): Any {
throw RuntimeException(
"Compilation units cannot be visited in an EvaluationVisitor. " +
"Utilize an EvaluationContext."
)
}
}
Loading

0 comments on commit 4f567eb

Please sign in to comment.