vm: very basic virtual machine

This commit is contained in:
2023-11-14 23:44:10 -08:00
parent 8c48c93663
commit 041848c14e
92 changed files with 1652 additions and 243 deletions

View File

@ -4,6 +4,7 @@ plugins {
dependencies {
api(project(":ast"))
api(project(":execution"))
api(project(":frontend"))
implementation(project(":common"))

View File

@ -1,78 +0,0 @@
package gay.pizza.pork.evaluator
import gay.pizza.pork.ast.gen.*
import gay.pizza.pork.frontend.ImportLocator
class CompilationUnitContext(
val compilationUnit: CompilationUnit,
val evaluator: Evaluator,
rootScope: Scope,
val name: String = "unknown"
) {
val internalScope = rootScope.fork("internal $name")
val externalScope = rootScope.fork("external $name")
private var initialized = false
fun initIfNeeded() {
if (initialized) {
return
}
initialized = true
processAllImports()
processAllDefinitions()
}
private fun processAllDefinitions() {
for (definition in compilationUnit.definitions) {
processDefinition(definition)
}
}
private fun processDefinition(definition: Definition) {
val internalValue = definitionValue(definition)
internalScope.define(definition.symbol.id, internalValue)
if (definition.modifiers.export) {
externalScope.define(definition.symbol.id, internalValue)
}
}
private fun definitionValue(definition: Definition): Any = when (definition) {
is FunctionDefinition -> FunctionContext(this, definition)
is LetDefinition -> {
EvaluationVisitor(internalScope.fork("let ${definition.symbol.id}"), CallStack())
.visit(definition.value)
}
}
private fun processAllImports() {
processPreludeImport()
val imports = compilationUnit.declarations.filterIsInstance<ImportDeclaration>()
for (import in imports) {
processImport(import)
}
}
private fun processImport(import: ImportDeclaration) {
val importPath = import.path.components.joinToString("/") { it.id } + ".pork"
val importLocator = ImportLocator(import.form.id, importPath)
val evaluationContext = evaluator.context(importLocator)
internalScope.inherit(evaluationContext.externalScope)
}
private fun processPreludeImport() {
processImport(preludeImport)
}
companion object {
private val preludeImport = ImportDeclaration(
Symbol("std"),
ImportPath(
listOf(
Symbol("lang"),
Symbol("prelude")
)
)
)
}
}

View File

@ -1,10 +1,11 @@
package gay.pizza.pork.evaluator
import gay.pizza.pork.ast.FunctionLevelVisitor
import gay.pizza.pork.ast.gen.*
import kotlin.math.abs
@Suppress("JavaIoSerializableObjectMustHaveReadResolve")
class EvaluationVisitor(root: Scope, val stack: CallStack) : NodeVisitor<Any> {
class EvaluationVisitor(root: Scope, val stack: CallStack) : FunctionLevelVisitor<Any>() {
private var currentScope: Scope = root
override fun visitIntegerLiteral(node: IntegerLiteral): Any = node.value
@ -66,10 +67,6 @@ class EvaluationVisitor(root: Scope, val stack: CallStack) : NodeVisitor<Any> {
return value
}
override fun visitLetDefinition(node: LetDefinition): Any {
topLevelUsedError("LetDefinition", "CompilationUnitContext")
}
override fun visitSymbolReference(node: SymbolReference): Any =
currentScope.value(node.symbol.id)
@ -383,18 +380,6 @@ class EvaluationVisitor(root: Scope, val stack: CallStack) : NodeVisitor<Any> {
}
}
override fun visitFunctionDefinition(node: FunctionDefinition): Any {
topLevelUsedError("FunctionDefinition", "FunctionContext")
}
override fun visitImportDeclaration(node: ImportDeclaration): Any {
topLevelUsedError("ImportDeclaration", "CompilationUnitContext")
}
override fun visitImportPath(node: ImportPath): Any {
topLevelUsedError("ImportPath", "CompilationUnitContext")
}
override fun visitIndexedBy(node: IndexedBy): Any {
val value = node.expression.visit(this)
val index = node.index.visit(this)
@ -410,14 +395,6 @@ class EvaluationVisitor(root: Scope, val stack: CallStack) : NodeVisitor<Any> {
throw RuntimeException("Failed to index '${value}' by '${index}': Unsupported types used.")
}
override fun visitCompilationUnit(node: CompilationUnit): Any {
topLevelUsedError("CompilationUnit", "CompilationUnitContext")
}
override fun visitNativeFunctionDescriptor(node: NativeFunctionDescriptor): Any {
topLevelUsedError("NativeFunctionDescriptor", "FunctionContext")
}
override fun visitNoneLiteral(node: NoneLiteral): Any = None
override fun visitContinue(node: Continue): Any = ContinueMarker
@ -445,13 +422,6 @@ class EvaluationVisitor(root: Scope, val stack: CallStack) : NodeVisitor<Any> {
throw RuntimeException("Can't perform $operation between floating point types")
}
private fun topLevelUsedError(name: String, alternative: String): Nothing {
throw RuntimeException(
"$name cannot be visited in an EvaluationVisitor. " +
"Utilize an $alternative instead."
)
}
private object BreakMarker : RuntimeException("Break Marker")
private object ContinueMarker: RuntimeException("Continue Marker")
}

View File

@ -1,25 +1,33 @@
package gay.pizza.pork.evaluator
import gay.pizza.pork.ast.gen.Symbol
import gay.pizza.pork.execution.ExecutionContext
import gay.pizza.pork.execution.ExecutionContextProvider
import gay.pizza.pork.frontend.ImportLocator
import gay.pizza.pork.frontend.Slab
import gay.pizza.pork.frontend.World
class Evaluator(val world: World, val scope: Scope) {
private val contexts = mutableMapOf<String, CompilationUnitContext>()
class Evaluator(val world: World) : ExecutionContextProvider {
private val scope = Scope.root()
private val contexts = mutableMapOf<Slab, SlabContext>()
private val nativeProviders = mutableMapOf<String, NativeProvider>()
fun evaluate(locator: ImportLocator): Scope =
context(locator).externalScope
fun context(locator: ImportLocator): CompilationUnitContext {
val unit = world.load(locator)
val identity = world.stableIdentity(locator)
val context = contexts.computeIfAbsent(identity) {
CompilationUnitContext(unit, this, scope, name = "${locator.form} ${locator.path}")
}
context.initIfNeeded()
return context
fun evaluate(locator: ImportLocator): Scope {
val slabContext = context(locator)
slabContext.finalizeScope()
return slabContext.externalScope
}
fun context(slab: Slab): SlabContext {
val slabContext = contexts.computeIfAbsent(slab) {
SlabContext(slab, this, scope)
}
slabContext.ensureImportedContextsExist()
return slabContext
}
fun context(locator: ImportLocator): SlabContext = context(world.load(locator))
fun nativeFunctionProvider(form: String): NativeProvider {
return nativeProviders[form] ?:
throw RuntimeException("Unknown native function form: $form")
@ -28,4 +36,10 @@ class Evaluator(val world: World, val scope: Scope) {
fun addNativeProvider(form: String, nativeProvider: NativeProvider) {
nativeProviders[form] = nativeProvider
}
override fun prepare(importLocator: ImportLocator, entryPointSymbol: Symbol): ExecutionContext {
val slab = context(importLocator)
slab.finalizeScope()
return EvaluatorExecutionContext(this, slab, entryPointSymbol)
}
}

View File

@ -0,0 +1,18 @@
package gay.pizza.pork.evaluator
import gay.pizza.pork.ast.gen.Symbol
import gay.pizza.pork.execution.ExecutionContext
class EvaluatorExecutionContext(val evaluator: Evaluator, val slab: SlabContext, val entryPointSymbol: Symbol) : ExecutionContext {
private val function: CallableFunction by lazy {
val value = slab.externalScope.value(entryPointSymbol.id)
if (value !is CallableFunction) {
throw RuntimeException("Symbol '${entryPointSymbol.id}' resolves to a non-function.")
}
value
}
override fun execute() {
function.call(emptyList(), CallStack())
}
}

View File

@ -2,16 +2,16 @@ package gay.pizza.pork.evaluator
import gay.pizza.pork.ast.gen.FunctionDefinition
class FunctionContext(val compilationUnitContext: CompilationUnitContext, val node: FunctionDefinition) : CallableFunction {
val name: String = "${compilationUnitContext.name} ${node.symbol.id}"
class FunctionContext(val slabContext: SlabContext, val node: FunctionDefinition) : CallableFunction {
val name: String by lazy { "${slabContext.slab.location.commonFriendlyName} ${node.symbol.id}" }
private fun resolveMaybeNative(): CallableFunction? = if (node.nativeFunctionDescriptor == null) {
null
} else {
val native = node.nativeFunctionDescriptor!!
val nativeFunctionProvider =
compilationUnitContext.evaluator.nativeFunctionProvider(native.form.id)
nativeFunctionProvider.provideNativeFunction(native.definitions.map { it.text }, node.arguments, compilationUnitContext)
slabContext.evaluator.nativeFunctionProvider(native.form.id)
nativeFunctionProvider.provideNativeFunction(native.definitions.map { it.text }, node.arguments, slabContext)
}
private val nativeCached by lazy { resolveMaybeNative() }
@ -21,7 +21,7 @@ class FunctionContext(val compilationUnitContext: CompilationUnitContext, val no
return nativeCached!!.call(arguments, stack)
}
val scope = compilationUnitContext.internalScope.fork(node.symbol.id)
val scope = slabContext.internalScope.fork(node.symbol.id)
for ((index, spec) in node.arguments.withIndex()) {
if (spec.multiple) {
val list = arguments.subList(index, arguments.size - 1)

View File

@ -18,7 +18,7 @@ class InternalNativeProvider(val quiet: Boolean = false) : NativeProvider {
override fun provideNativeFunction(
definitions: List<String>,
arguments: List<ArgumentSpec>,
inside: CompilationUnitContext
inside: SlabContext
): CallableFunction {
val definition = definitions[0]
return functions[definition] ?: throw RuntimeException("Unknown Internal Function: $definition")

View File

@ -3,5 +3,5 @@ package gay.pizza.pork.evaluator
import gay.pizza.pork.ast.gen.ArgumentSpec
interface NativeProvider {
fun provideNativeFunction(definitions: List<String>, arguments: List<ArgumentSpec>, inside: CompilationUnitContext): CallableFunction
fun provideNativeFunction(definitions: List<String>, arguments: List<ArgumentSpec>, inside: SlabContext): CallableFunction
}

View File

@ -0,0 +1,62 @@
package gay.pizza.pork.evaluator
import gay.pizza.pork.ast.gen.Definition
import gay.pizza.pork.ast.gen.FunctionDefinition
import gay.pizza.pork.ast.gen.LetDefinition
import gay.pizza.pork.ast.gen.visit
import gay.pizza.pork.frontend.Slab
class SlabContext(val slab: Slab, val evaluator: Evaluator, rootScope: Scope) {
val internalScope = rootScope.fork("internal ${slab.location.commonFriendlyName}")
val externalScope = rootScope.fork("external ${slab.location.commonFriendlyName}")
init {
processAllDefinitions()
}
fun ensureImportedContextsExist() {
for (importedSlab in slab.importedSlabs) {
evaluator.context(importedSlab)
}
}
private var initializedFinalScope = false
fun finalizeScope() {
if (initializedFinalScope) {
return
}
initializedFinalScope = true
processFinalImportScopes()
}
private fun processAllDefinitions() {
for (definition in slab.compilationUnit.definitions) {
processDefinition(definition)
}
}
private fun processDefinition(definition: Definition) {
val internalValue = definitionValue(definition)
internalScope.define(definition.symbol.id, internalValue)
if (definition.modifiers.export) {
externalScope.define(definition.symbol.id, internalValue)
}
}
private fun definitionValue(definition: Definition): Any = when (definition) {
is FunctionDefinition -> FunctionContext(this, definition)
is LetDefinition -> {
EvaluationVisitor(internalScope.fork("let ${definition.symbol.id}"), CallStack())
.visit(definition.value)
}
}
private fun processFinalImportScopes() {
for (importedSlab in slab.importedSlabs) {
val importedSlabContext = evaluator.context(importedSlab)
importedSlabContext.processFinalImportScopes()
internalScope.inherit(importedSlabContext.externalScope)
}
}
}