vm: very basic virtual machine

This commit is contained in:
Alex Zenla 2023-11-14 23:44:10 -08:00
parent 8c48c93663
commit 041848c14e
Signed by: alex
GPG Key ID: C0780728420EBFE5
92 changed files with 1652 additions and 243 deletions

View File

@ -0,0 +1,42 @@
package gay.pizza.pork.ast
import gay.pizza.pork.ast.gen.*
abstract class FunctionLevelVisitor<T> : NodeVisitor<T> {
override fun visitForInItem(node: ForInItem): T =
throw RuntimeException("Visiting ForInItem is not supported.")
override fun visitSymbol(node: Symbol): T =
throw RuntimeException("Visiting Symbol is not supported.")
override fun visitLetDefinition(node: LetDefinition): T {
topLevelUsedError("LetDefinition")
}
override fun visitArgumentSpec(node: ArgumentSpec): T =
throw RuntimeException("Visiting ArgumentSpec is not supported.")
override fun visitFunctionDefinition(node: FunctionDefinition): T {
topLevelUsedError("FunctionDefinition")
}
override fun visitImportDeclaration(node: ImportDeclaration): T {
topLevelUsedError("ImportDeclaration")
}
override fun visitImportPath(node: ImportPath): T {
topLevelUsedError("ImportPath")
}
override fun visitCompilationUnit(node: CompilationUnit): T {
topLevelUsedError("CompilationUnit")
}
override fun visitNativeFunctionDescriptor(node: NativeFunctionDescriptor): T {
topLevelUsedError("NativeFunctionDescriptor")
}
private fun topLevelUsedError(name: String): Nothing {
throw RuntimeException("$name cannot be visited in a FunctionVisitor.")
}
}

View File

@ -0,0 +1,7 @@
plugins {
id("gay.pizza.pork.module")
}
dependencies {
implementation(project(":common"))
}

View File

@ -0,0 +1,10 @@
package gay.pizza.pork.bytecode
import kotlinx.serialization.Serializable
@Serializable
data class CompiledWorld(
val constantPool: ConstantPool,
val symbolTable: SymbolTable,
val code: List<Op>
)

View File

@ -0,0 +1,3 @@
package gay.pizza.pork.bytecode
class Constant(val id: UInt, val value: ByteArray)

View File

@ -0,0 +1,6 @@
package gay.pizza.pork.bytecode
import kotlinx.serialization.Serializable
@Serializable
data class ConstantPool(val constants: List<ByteArray>)

View File

@ -0,0 +1,18 @@
package gay.pizza.pork.bytecode
class MutableConstantPool {
private val pool = mutableListOf<Constant>()
fun assign(content: ByteArray): UInt {
for (constant in pool) {
if (constant.value.contentEquals(content)) {
return constant.id
}
}
val id = pool.size.toUInt()
pool.add(Constant(id, content))
return id
}
fun all(): List<Constant> = pool
}

View File

@ -0,0 +1,3 @@
package gay.pizza.pork.bytecode
class MutableRel(var rel: UInt)

View File

@ -0,0 +1,6 @@
package gay.pizza.pork.bytecode
import kotlinx.serialization.Serializable
@Serializable
data class Op(val code: Opcode, val args: List<UInt>)

View File

@ -0,0 +1,43 @@
package gay.pizza.pork.bytecode
enum class Opcode(val id: UByte) {
Constant(1u),
None(2u),
False(3u),
True(4u),
Pop(5u),
Jump(6u),
JumpIf(7u),
Not(8u),
UnaryPlus(9u),
UnaryMinus(10u),
BinaryNot(11u),
And(20u),
Native(24u),
Return(10u),
StoreLocal(16u),
LoadLocal(17u),
Add(18u),
Subtract(19u),
Multiply(20u),
Divide(21u),
CompareEqual(22u),
CompareLesser(23u),
CompareGreater(24u),
CompareLesserEqual(25u),
CompareGreaterEqual(26u),
Or(27u),
BinaryAnd(28u),
BinaryOr(29u),
BinaryXor(30u),
List(31u),
Integer(32u),
Double(33u),
Call(34u),
EuclideanModulo(35u),
Remainder(36u),
Index(37u),
ScopeIn(38u),
ScopeOut(39u),
End(255u),
}

View File

@ -0,0 +1,3 @@
package gay.pizza.pork.bytecode
class Ops(val ops: List<Op>)

View File

@ -0,0 +1,10 @@
package gay.pizza.pork.bytecode
import kotlinx.serialization.Serializable
@Serializable
data class SymbolInfo(
val id: String,
val offset: UInt,
val size: UInt
)

View File

@ -0,0 +1,8 @@
package gay.pizza.pork.bytecode
import kotlinx.serialization.Serializable
@Serializable
data class SymbolTable(
val symbols: List<SymbolInfo>
)

View File

@ -0,0 +1,40 @@
package gay.pizza.pork.common
object ByteRepresentation {
fun encode(value: Int): ByteArray {
val buffer = ByteArray(4)
encode(value, buffer, 0)
return buffer
}
fun encode(value: Long): ByteArray {
val buffer = ByteArray(8)
encode(value, buffer, 0)
return buffer
}
fun encode(value: Double): ByteArray =
encode(value.toRawBits())
fun encode(value: Int, buffer: ByteArray, offset: Int) {
buffer[offset + 0] = (value shr 0).toByte()
buffer[offset + 1] = (value shr 8).toByte()
buffer[offset + 2] = (value shr 16).toByte()
buffer[offset + 3] = (value shr 24).toByte()
}
fun encode(value: Long, buffer: ByteArray, offset: Int) {
buffer[offset + 0] = (value shr 0).toByte()
buffer[offset + 1] = (value shr 8).toByte()
buffer[offset + 2] = (value shr 16).toByte()
buffer[offset + 3] = (value shr 24).toByte()
buffer[offset + 4] = (value shr 32).toByte()
buffer[offset + 5] = (value shr 40).toByte()
buffer[offset + 6] = (value shr 48).toByte()
buffer[offset + 7] = (value shr 56).toByte()
}
fun encode(value: Double, buffer: ByteArray, offset: Int) {
encode(value.toRawBits(), buffer, offset)
}
}

11
compiler/build.gradle.kts Normal file
View File

@ -0,0 +1,11 @@
plugins {
id("gay.pizza.pork.module")
}
dependencies {
api(project(":ast"))
api(project(":bytecode"))
api(project(":parser"))
api(project(":frontend"))
implementation(project(":common"))
}

View File

@ -0,0 +1,16 @@
package gay.pizza.pork.compiler
import gay.pizza.pork.ast.gen.Symbol
import gay.pizza.pork.frontend.Slab
class CompilableSlab(val compiler: Compiler, val slab: Slab) {
val compilableSymbols: List<CompilableSymbol> by lazy {
slab.scope.internalSymbols.map { symbol ->
CompilableSymbol(this, symbol)
}
}
fun compilableSymbolOf(symbol: Symbol): CompilableSymbol? = compilableSymbols.firstOrNull {
it.scopeSymbol.symbol == symbol
}
}

View File

@ -0,0 +1,34 @@
package gay.pizza.pork.compiler
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.scope.ScopeSymbol
class CompilableSymbol(val compilableSlab: CompilableSlab, val scopeSymbol: ScopeSymbol) {
val compiledStubOps: List<StubOp> by lazy { compile() }
val usedSymbols: List<ScopeSymbol>
get() = scopeSymbol.scope.usedSymbols
private fun compile(): List<StubOp> {
val emitter = StubOpEmitter(compilableSlab.compiler, this)
emitter.enter()
val what = if (scopeSymbol.definition is FunctionDefinition) {
val functionDefinition = scopeSymbol.definition as FunctionDefinition
emitter.allocateOuterScope(functionDefinition)
functionDefinition.block ?: functionDefinition.nativeFunctionDescriptor!!
} else {
val letDefinition = scopeSymbol.definition as LetDefinition
letDefinition.value
}
emitter.visit(what)
emitter.exit()
return emitter.ops()
}
val id: String
get() = "${compilableSlab.slab.location.commonFriendlyName} ${scopeSymbol.symbol.id}"
override fun toString(): String = "${compilableSlab.slab.location.commonFriendlyName} ${scopeSymbol.symbol.id}"
}

View File

@ -0,0 +1,38 @@
package gay.pizza.pork.compiler
import gay.pizza.pork.bytecode.*
class CompiledWorldLayout(val compiler: Compiler) : StubResolutionContext {
private val allStubOps = mutableListOf<StubOp>()
private val symbolTable = mutableMapOf<CompilableSymbol, SymbolInfo>()
fun add(symbol: CompilableSymbol) {
val start = allStubOps.size
val stubOps = symbol.compiledStubOps
symbolTable[symbol] = SymbolInfo(symbol.id, start.toUInt(), stubOps.size.toUInt())
allStubOps.addAll(stubOps)
}
private fun patch(): List<Op> {
val ops = mutableListOf<Op>()
for (stub in allStubOps) {
val actualArguments = stub.op.args.toMutableList()
stub.patch(this, actualArguments)
ops.add(Op(stub.op.code, actualArguments))
}
return ops
}
override fun resolveJumpTarget(symbol: CompilableSymbol): UInt {
return symbolTable[symbol]?.offset ?:
throw RuntimeException("Unable to resolve jump target: ${symbol.scopeSymbol.symbol.id}")
}
fun layoutCompiledWorld(): CompiledWorld {
val constantPool = mutableListOf<ByteArray>()
for (item in compiler.constantPool.all()) {
constantPool.add(item.value)
}
return CompiledWorld(ConstantPool(constantPool), SymbolTable(symbolTable.values.toList()), patch())
}
}

View File

@ -0,0 +1,47 @@
package gay.pizza.pork.compiler
import gay.pizza.pork.bytecode.CompiledWorld
import gay.pizza.pork.bytecode.MutableConstantPool
import gay.pizza.pork.frontend.Slab
import gay.pizza.pork.frontend.scope.ScopeSymbol
class Compiler {
val constantPool: MutableConstantPool = MutableConstantPool()
val compilableSlabs: ComputableState<Slab, CompilableSlab> = ComputableState { slab ->
CompilableSlab(this, slab)
}
fun resolveOrNull(scopeSymbol: ScopeSymbol): CompilableSymbol? {
val compiledSlab = compilableSlabs.of(scopeSymbol.slabScope.slab)
return compiledSlab.compilableSymbolOf(scopeSymbol.symbol)
}
fun resolve(scopeSymbol: ScopeSymbol): CompilableSymbol = resolveOrNull(scopeSymbol) ?:
throw RuntimeException(
"Unable to resolve scope symbol: " +
"${scopeSymbol.slabScope.slab.location.commonFriendlyName} ${scopeSymbol.symbol.id}")
fun contributeCompiledSymbols(
into: MutableSet<CompilableSymbol>,
symbol: ScopeSymbol,
resolved: CompilableSymbol = resolve(symbol)
) {
if (!into.add(resolved)) {
return
}
for (used in resolved.usedSymbols) {
contributeCompiledSymbols(into, used)
}
}
fun compile(entryPointSymbol: CompilableSymbol): CompiledWorld {
val usedSymbolSet = mutableSetOf<CompilableSymbol>()
contributeCompiledSymbols(usedSymbolSet, entryPointSymbol.scopeSymbol, entryPointSymbol)
val layout = CompiledWorldLayout(this)
for (used in usedSymbolSet) {
layout.add(used)
}
return layout.layoutCompiledWorld()
}
}

View File

@ -0,0 +1,7 @@
package gay.pizza.pork.compiler
class ComputableState<X, T>(val computation: (X) -> T) {
private val state = StoredState<X, T>()
fun of(key: X): T = state.computeIfAbsent(key, computation)
}

View File

@ -0,0 +1,11 @@
package gay.pizza.pork.compiler
import gay.pizza.pork.bytecode.MutableRel
class LoopState(
val startOfLoop: UInt,
val exitJumpTarget: MutableRel,
val body: MutableRel,
val scopeDepth: Int,
val enclosing: LoopState? = null
)

View File

@ -0,0 +1,12 @@
package gay.pizza.pork.compiler
import gay.pizza.pork.bytecode.MutableRel
import gay.pizza.pork.bytecode.Op
class PatchRelOp(op: Op, val index: Int, val symbol: CompilableSymbol, val rel: MutableRel) : StubOp(op) {
override fun patch(context: StubResolutionContext, arguments: MutableList<UInt>) {
arguments[index] = context.resolveJumpTarget(symbol) + rel.rel
}
override fun toString(): String = "PatchRelOp(${op}, ${index}, ${symbol}, ${rel})"
}

View File

@ -0,0 +1,13 @@
package gay.pizza.pork.compiler
import gay.pizza.pork.bytecode.Op
class PatchSymOp(op: Op, val patches: Map<Int, CompilableSymbol>) : StubOp(op) {
override fun patch(context: StubResolutionContext, arguments: MutableList<UInt>) {
for ((index, symbol) in patches) {
arguments[index] = context.resolveJumpTarget(symbol)
}
}
override fun toString(): String = "PatchSymOp(${op}, ${patches})"
}

View File

@ -0,0 +1,7 @@
package gay.pizza.pork.compiler
import gay.pizza.pork.bytecode.Op
class StaticOp(op: Op) : StubOp(op) {
override fun toString(): String = "StaticOp(${op})"
}

View File

@ -0,0 +1,11 @@
package gay.pizza.pork.compiler
class StoredState<X, T> {
private val state = mutableMapOf<X, T>()
fun computeIfAbsent(key: X, computation: (X) -> T): T = state.computeIfAbsent(key, computation)
fun of(key: X): T? = state[key]
fun put(key: X, value: T) {
state[key] = value
}
}

View File

@ -0,0 +1,7 @@
package gay.pizza.pork.compiler
import gay.pizza.pork.bytecode.Op
abstract class StubOp(val op: Op) {
open fun patch(context: StubResolutionContext, arguments: MutableList<UInt>) {}
}

View File

@ -0,0 +1,336 @@
package gay.pizza.pork.compiler
import gay.pizza.pork.ast.FunctionLevelVisitor
import gay.pizza.pork.ast.gen.*
import gay.pizza.pork.bytecode.MutableRel
import gay.pizza.pork.bytecode.Op
import gay.pizza.pork.bytecode.Opcode
class StubOpEmitter(val compiler: Compiler, val symbol: CompilableSymbol) : FunctionLevelVisitor<Unit>() {
private val ops = mutableListOf<StubOp>()
private var loopState: LoopState? = null
private val localVariables = mutableListOf<MutableList<StubVar>>()
private var localVarIndex = 0u
private val requiredLoopState: LoopState
get() {
if (loopState != null) {
return loopState!!
}
throw RuntimeException("loopState expected but was not found.")
}
private fun allocateLocalVariable(symbol: Symbol): StubVar {
val scope = localVariables.last()
val variable = StubVar(localVarIndex++, symbol)
scope.add(variable)
return variable
}
private fun resolveSymbol(symbol: Symbol): CallOrStubVar {
for (scope in localVariables.reversed()) {
val found = scope.firstOrNull { it.symbol == symbol }
if (found != null) {
return CallOrStubVar(stubVar = found)
}
}
val found = this.symbol.compilableSlab.compilableSymbolOf(symbol)
if (found != null) {
return CallOrStubVar(call = found)
}
throw RuntimeException("Unable to resolve symbol: ${symbol.id}")
}
private fun pushScope() {
emit(Opcode.ScopeIn)
localVariables.add(mutableListOf())
}
private fun popScope() {
emit(Opcode.ScopeOut)
localVariables.removeLast()
}
fun enter() {
pushScope()
}
fun allocateOuterScope(definition: FunctionDefinition) {
val allNormalArguments = definition.arguments.takeWhile { !it.multiple }
val varArgument = definition.arguments.firstOrNull { it.multiple }
for (arg in allNormalArguments.reversed()) {
val functionLocal = allocateLocalVariable(arg.symbol)
emit(Opcode.StoreLocal, listOf(functionLocal.index))
}
if (varArgument != null) {
val functionLocal = allocateLocalVariable(varArgument.symbol)
emit(Opcode.StoreLocal, listOf(functionLocal.index))
}
}
fun exit() {
popScope()
emit(Opcode.Return)
}
override fun visitBlock(node: Block) {
pushScope()
node.visitChildren(this)
popScope()
}
override fun visitBooleanLiteral(node: BooleanLiteral) {
emit(if (node.value) Opcode.True else Opcode.False)
}
override fun visitBreak(node: Break) {
patch(Opcode.Jump, listOf(0u), 0, symbol, requiredLoopState.exitJumpTarget)
}
override fun visitContinue(node: Continue) {
patch(Opcode.Jump, listOf(0u), 0, symbol, requiredLoopState.startOfLoop)
}
override fun visitDoubleLiteral(node: DoubleLiteral) {
emit(Opcode.Integer, listOf(node.value.toUInt()))
}
override fun visitForIn(node: ForIn) {
TODO("ForIn is currently unsupported")
}
override fun visitFunctionCall(node: FunctionCall) {
val targetScopeSymbol = symbol.scopeSymbol.scope.resolve(node.symbol) ?:
throw RuntimeException("Unable to resolve symbol: ${node.symbol.id}")
val targetSymbol = compiler.resolve(targetScopeSymbol)
val functionDefinition = targetSymbol.scopeSymbol.definition as FunctionDefinition
val retRel = MutableRel(0u)
patch(Opcode.Integer, listOf(0u), 0, symbol, retRel)
val normalArguments = mutableListOf<Expression>()
var variableArguments: List<Expression>? = null
for ((index, item) in functionDefinition.arguments.zip(node.arguments).withIndex()) {
val (spec, value) = item
if (spec.multiple) {
val remaining = node.arguments.drop(index)
variableArguments = remaining
break
} else {
normalArguments.add(value)
}
}
if (variableArguments != null) {
for (item in variableArguments.reversed()) {
item.visit(this)
}
emit(Opcode.List, listOf(variableArguments.size.toUInt()))
}
for (item in normalArguments.reversed()) {
visit(item)
}
retRel.rel = (ops.size + 1).toUInt()
patch(Opcode.Call, listOf(0u), mapOf(0 to targetSymbol))
}
override fun visitIf(node: If) {
val thenRel = MutableRel(0u)
val endRel = MutableRel(0u)
node.condition.visit(this)
patch(Opcode.JumpIf, listOf(0u), 0, symbol, thenRel)
node.elseBlock?.visit(this)
patch(Opcode.Jump, listOf(0u), 0, symbol, endRel)
thenRel.rel = ops.size.toUInt()
node.thenBlock.visit(this)
endRel.rel = ops.size.toUInt()
}
override fun visitIndexedBy(node: IndexedBy) {
node.expression.visit(this)
node.index.visit(this)
emit(Opcode.Index)
}
override fun visitInfixOperation(node: InfixOperation) {
node.left.visit(this)
node.right.visit(this)
when (node.op) {
InfixOperator.Plus -> emit(Opcode.Add)
InfixOperator.Minus -> emit(Opcode.Subtract)
InfixOperator.Multiply -> emit(Opcode.Multiply)
InfixOperator.Divide -> emit(Opcode.Divide)
InfixOperator.Equals -> emit(Opcode.CompareEqual)
InfixOperator.NotEquals -> {
emit(Opcode.CompareEqual)
emit(Opcode.Not)
}
InfixOperator.EuclideanModulo -> emit(Opcode.EuclideanModulo)
InfixOperator.Remainder -> emit(Opcode.Remainder)
InfixOperator.Lesser -> emit(Opcode.CompareLesser)
InfixOperator.Greater -> emit(Opcode.CompareGreater)
InfixOperator.GreaterEqual -> emit(Opcode.CompareGreaterEqual)
InfixOperator.LesserEqual -> emit(Opcode.CompareLesserEqual)
InfixOperator.BooleanAnd -> emit(Opcode.And)
InfixOperator.BooleanOr -> emit(Opcode.Or)
InfixOperator.BinaryAnd -> emit(Opcode.BinaryAnd)
InfixOperator.BinaryOr -> emit(Opcode.BinaryOr)
InfixOperator.BinaryExclusiveOr -> emit(Opcode.BinaryXor)
}
}
override fun visitIntegerLiteral(node: IntegerLiteral) {
emit(Opcode.Integer, listOf(node.value.toUInt()))
}
override fun visitLetAssignment(node: LetAssignment) {
val variable = allocateLocalVariable(node.symbol)
node.value.visit(this)
emit(Opcode.StoreLocal, listOf(variable.index))
}
override fun visitListLiteral(node: ListLiteral) {
val count = node.items.size
for (item in node.items) {
item.visit(this)
}
emit(Opcode.List, listOf(count.toUInt()))
}
override fun visitLongLiteral(node: LongLiteral) {
emit(Opcode.Integer, listOf(node.value.toUInt()))
}
override fun visitNoneLiteral(node: NoneLiteral) {
emit(Opcode.None)
}
override fun visitParentheses(node: Parentheses) {
node.expression.visit(this)
}
override fun visitPrefixOperation(node: PrefixOperation) {
node.expression.visit(this)
when (node.op) {
PrefixOperator.BooleanNot -> emit(Opcode.Not)
PrefixOperator.UnaryPlus -> emit(Opcode.UnaryPlus)
PrefixOperator.UnaryMinus -> emit(Opcode.UnaryMinus)
PrefixOperator.BinaryNot -> emit(Opcode.BinaryNot)
}
}
override fun visitSetAssignment(node: SetAssignment) {
val stubVarOrCall = resolveSymbol(node.symbol)
if (stubVarOrCall.stubVar == null) {
throw RuntimeException("Invalid set assignment.")
}
node.value.visit(this)
emit(Opcode.StoreLocal, listOf(stubVarOrCall.stubVar.index))
}
override fun visitStringLiteral(node: StringLiteral) {
val bytes = node.text.toByteArray()
val constant = compiler.constantPool.assign(bytes)
emit(Opcode.Constant, listOf(constant))
}
override fun visitSuffixOperation(node: SuffixOperation) {
val stubVarOrCall = resolveSymbol(node.reference.symbol)
if (stubVarOrCall.stubVar == null) {
throw RuntimeException("Invalid suffix operation.")
}
load(stubVarOrCall)
when (node.op) {
SuffixOperator.Increment -> {
emit(Opcode.Integer, listOf(1u))
emit(Opcode.Add, emptyList())
emit(Opcode.StoreLocal, listOf(stubVarOrCall.stubVar.index))
}
SuffixOperator.Decrement -> {
emit(Opcode.Integer, listOf(1u))
emit(Opcode.Subtract, emptyList())
emit(Opcode.StoreLocal, listOf(stubVarOrCall.stubVar.index))
}
}
}
override fun visitSymbolReference(node: SymbolReference) {
val variable = resolveSymbol(node.symbol)
load(variable)
}
override fun visitVarAssignment(node: VarAssignment) {
val variable = allocateLocalVariable(node.symbol)
node.value.visit(this)
emit(Opcode.StoreLocal, listOf(variable.index))
}
override fun visitWhile(node: While) {
val startOfBody = MutableRel(0u)
val endOfLoop = MutableRel(0u)
val currentLoopState = LoopState(
startOfLoop = ops.size.toUInt(),
exitJumpTarget = endOfLoop,
body = startOfBody,
scopeDepth = (loopState?.scopeDepth ?: 0) + 1,
enclosing = loopState
)
loopState = currentLoopState
node.condition.visit(this)
patch(Opcode.JumpIf, listOf(0u), 0, symbol, startOfBody)
patch(Opcode.Jump, listOf(0u), 0, symbol, endOfLoop)
startOfBody.rel = ops.size.toUInt()
node.block.visit(this)
patch(Opcode.Jump, listOf(0u), 0, symbol, currentLoopState.startOfLoop)
endOfLoop.rel = ops.size.toUInt()
}
override fun visitNativeFunctionDescriptor(node: NativeFunctionDescriptor) {
for (def in node.definitions) {
val defConstant = compiler.constantPool.assign(def.text.toByteArray())
emit(Opcode.Constant, listOf(defConstant))
}
val formConstant = compiler.constantPool.assign(node.form.id.toByteArray())
emit(Opcode.Native, listOf(formConstant, node.definitions.size.toUInt()))
}
private fun emit(code: Opcode) {
emit(code, emptyList())
}
private fun emit(code: Opcode, arguments: List<UInt>) {
ops.add(StaticOp(Op(code, arguments)))
}
private fun patch(code: Opcode, arguments: List<UInt>, index: Int, symbol: CompilableSymbol, rel: MutableRel) {
ops.add(PatchRelOp(Op(code, arguments), index, symbol, rel))
}
private fun patch(code: Opcode, arguments: List<UInt>, index: Int, symbol: CompilableSymbol, rel: UInt) {
ops.add(PatchRelOp(Op(code, arguments), index, symbol, MutableRel(rel)))
}
private fun patch(code: Opcode, arguments: List<UInt>, patches: Map<Int, CompilableSymbol>) {
ops.add(PatchSymOp(Op(code, arguments), patches))
}
fun ops(): List<StubOp> = ops
private fun load(callOrStubVar: CallOrStubVar) {
if (callOrStubVar.stubVar != null) {
emit(Opcode.LoadLocal, listOf(callOrStubVar.stubVar.index))
} else {
emit(Opcode.Integer, listOf(ops.size.toUInt() + 2u))
patch(Opcode.Call, listOf(0u), mapOf(0 to callOrStubVar.call!!))
}
}
private class CallOrStubVar(
val call: CompilableSymbol? = null,
val stubVar: StubVar? = null
)
}

View File

@ -0,0 +1,5 @@
package gay.pizza.pork.compiler
interface StubResolutionContext {
fun resolveJumpTarget(symbol: CompilableSymbol): UInt
}

View File

@ -0,0 +1,8 @@
package gay.pizza.pork.compiler
import gay.pizza.pork.ast.gen.Symbol
class StubVar(
val index: UInt,
val symbol: Symbol
)

View File

@ -4,6 +4,7 @@ plugins {
dependencies { dependencies {
api(project(":ast")) api(project(":ast"))
api(project(":execution"))
api(project(":frontend")) api(project(":frontend"))
implementation(project(":common")) 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 package gay.pizza.pork.evaluator
import gay.pizza.pork.ast.FunctionLevelVisitor
import gay.pizza.pork.ast.gen.* import gay.pizza.pork.ast.gen.*
import kotlin.math.abs import kotlin.math.abs
@Suppress("JavaIoSerializableObjectMustHaveReadResolve") @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 private var currentScope: Scope = root
override fun visitIntegerLiteral(node: IntegerLiteral): Any = node.value override fun visitIntegerLiteral(node: IntegerLiteral): Any = node.value
@ -66,10 +67,6 @@ class EvaluationVisitor(root: Scope, val stack: CallStack) : NodeVisitor<Any> {
return value return value
} }
override fun visitLetDefinition(node: LetDefinition): Any {
topLevelUsedError("LetDefinition", "CompilationUnitContext")
}
override fun visitSymbolReference(node: SymbolReference): Any = override fun visitSymbolReference(node: SymbolReference): Any =
currentScope.value(node.symbol.id) 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 { override fun visitIndexedBy(node: IndexedBy): Any {
val value = node.expression.visit(this) val value = node.expression.visit(this)
val index = node.index.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.") 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 visitNoneLiteral(node: NoneLiteral): Any = None
override fun visitContinue(node: Continue): Any = ContinueMarker 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") 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 BreakMarker : RuntimeException("Break Marker")
private object ContinueMarker: RuntimeException("Continue Marker") private object ContinueMarker: RuntimeException("Continue Marker")
} }

View File

@ -1,25 +1,33 @@
package gay.pizza.pork.evaluator 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.ImportLocator
import gay.pizza.pork.frontend.Slab
import gay.pizza.pork.frontend.World import gay.pizza.pork.frontend.World
class Evaluator(val world: World, val scope: Scope) { class Evaluator(val world: World) : ExecutionContextProvider {
private val contexts = mutableMapOf<String, CompilationUnitContext>() private val scope = Scope.root()
private val contexts = mutableMapOf<Slab, SlabContext>()
private val nativeProviders = mutableMapOf<String, NativeProvider>() private val nativeProviders = mutableMapOf<String, NativeProvider>()
fun evaluate(locator: ImportLocator): Scope = fun evaluate(locator: ImportLocator): Scope {
context(locator).externalScope val slabContext = context(locator)
slabContext.finalizeScope()
return slabContext.externalScope
}
fun context(locator: ImportLocator): CompilationUnitContext { fun context(slab: Slab): SlabContext {
val unit = world.load(locator) val slabContext = contexts.computeIfAbsent(slab) {
val identity = world.stableIdentity(locator) SlabContext(slab, this, scope)
val context = contexts.computeIfAbsent(identity) {
CompilationUnitContext(unit, this, scope, name = "${locator.form} ${locator.path}")
} }
context.initIfNeeded() slabContext.ensureImportedContextsExist()
return context return slabContext
} }
fun context(locator: ImportLocator): SlabContext = context(world.load(locator))
fun nativeFunctionProvider(form: String): NativeProvider { fun nativeFunctionProvider(form: String): NativeProvider {
return nativeProviders[form] ?: return nativeProviders[form] ?:
throw RuntimeException("Unknown native function form: $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) { fun addNativeProvider(form: String, nativeProvider: NativeProvider) {
nativeProviders[form] = 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 import gay.pizza.pork.ast.gen.FunctionDefinition
class FunctionContext(val compilationUnitContext: CompilationUnitContext, val node: FunctionDefinition) : CallableFunction { class FunctionContext(val slabContext: SlabContext, val node: FunctionDefinition) : CallableFunction {
val name: String = "${compilationUnitContext.name} ${node.symbol.id}" val name: String by lazy { "${slabContext.slab.location.commonFriendlyName} ${node.symbol.id}" }
private fun resolveMaybeNative(): CallableFunction? = if (node.nativeFunctionDescriptor == null) { private fun resolveMaybeNative(): CallableFunction? = if (node.nativeFunctionDescriptor == null) {
null null
} else { } else {
val native = node.nativeFunctionDescriptor!! val native = node.nativeFunctionDescriptor!!
val nativeFunctionProvider = val nativeFunctionProvider =
compilationUnitContext.evaluator.nativeFunctionProvider(native.form.id) slabContext.evaluator.nativeFunctionProvider(native.form.id)
nativeFunctionProvider.provideNativeFunction(native.definitions.map { it.text }, node.arguments, compilationUnitContext) nativeFunctionProvider.provideNativeFunction(native.definitions.map { it.text }, node.arguments, slabContext)
} }
private val nativeCached by lazy { resolveMaybeNative() } private val nativeCached by lazy { resolveMaybeNative() }
@ -21,7 +21,7 @@ class FunctionContext(val compilationUnitContext: CompilationUnitContext, val no
return nativeCached!!.call(arguments, stack) 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()) { for ((index, spec) in node.arguments.withIndex()) {
if (spec.multiple) { if (spec.multiple) {
val list = arguments.subList(index, arguments.size - 1) val list = arguments.subList(index, arguments.size - 1)

View File

@ -18,7 +18,7 @@ class InternalNativeProvider(val quiet: Boolean = false) : NativeProvider {
override fun provideNativeFunction( override fun provideNativeFunction(
definitions: List<String>, definitions: List<String>,
arguments: List<ArgumentSpec>, arguments: List<ArgumentSpec>,
inside: CompilationUnitContext inside: SlabContext
): CallableFunction { ): CallableFunction {
val definition = definitions[0] val definition = definitions[0]
return functions[definition] ?: throw RuntimeException("Unknown Internal Function: $definition") 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 import gay.pizza.pork.ast.gen.ArgumentSpec
interface NativeProvider { 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)
}
}
}

View File

@ -1,9 +1,11 @@
let count = 5
export func main() { export func main() {
var x = 1 var x = 1
while x <= count { while x <= 5 {
println(x) if x == 3 {
println("The value is 3")
} else {
println("The value is not 3")
}
x++ x++
} }
} }

View File

@ -0,0 +1,8 @@
plugins {
id("gay.pizza.pork.module")
}
dependencies {
api(project(":frontend"))
implementation(project(":common"))
}

View File

@ -0,0 +1,5 @@
package gay.pizza.pork.execution
interface ExecutionContext {
fun execute()
}

View File

@ -0,0 +1,8 @@
package gay.pizza.pork.execution
import gay.pizza.pork.ast.gen.Symbol
import gay.pizza.pork.frontend.ImportLocator
interface ExecutionContextProvider {
fun prepare(importLocator: ImportLocator, entryPointSymbol: Symbol): ExecutionContext
}

View File

@ -25,7 +25,7 @@ class FfiNativeProvider : NativeProvider {
override fun provideNativeFunction( override fun provideNativeFunction(
definitions: List<String>, definitions: List<String>,
arguments: List<ArgumentSpec>, arguments: List<ArgumentSpec>,
inside: CompilationUnitContext inside: SlabContext
): CallableFunction { ): CallableFunction {
if (definitions[0] == "internal") { if (definitions[0] == "internal") {
val internal = internalFunctions[definitions[1]] ?: val internal = internalFunctions[definitions[1]] ?:
@ -73,7 +73,7 @@ class FfiNativeProvider : NativeProvider {
} }
} }
private fun addStructDefs(ffiTypeRegistry: FfiTypeRegistry, types: List<String>, inside: CompilationUnitContext) { private fun addStructDefs(ffiTypeRegistry: FfiTypeRegistry, types: List<String>, inside: SlabContext) {
for (parameter in types) { for (parameter in types) {
if (!parameter.startsWith("struct ")) { if (!parameter.startsWith("struct ")) {
continue continue

View File

@ -15,5 +15,5 @@ object JavaAutogenContentSource : ContentSource {
return StringCharSource(content) return StringCharSource(content)
} }
override fun stableContentIdentity(path: String): String = path override fun stableContentPath(path: String): String = path
} }

View File

@ -2,7 +2,7 @@ package gay.pizza.pork.ffi
import gay.pizza.pork.ast.gen.ArgumentSpec import gay.pizza.pork.ast.gen.ArgumentSpec
import gay.pizza.pork.evaluator.CallableFunction import gay.pizza.pork.evaluator.CallableFunction
import gay.pizza.pork.evaluator.CompilationUnitContext import gay.pizza.pork.evaluator.SlabContext
import gay.pizza.pork.evaluator.NativeProvider import gay.pizza.pork.evaluator.NativeProvider
import gay.pizza.pork.evaluator.None import gay.pizza.pork.evaluator.None
import java.lang.invoke.MethodHandles import java.lang.invoke.MethodHandles
@ -14,7 +14,7 @@ class JavaNativeProvider : NativeProvider {
override fun provideNativeFunction( override fun provideNativeFunction(
definitions: List<String>, definitions: List<String>,
arguments: List<ArgumentSpec>, arguments: List<ArgumentSpec>,
inside: CompilationUnitContext inside: SlabContext
): CallableFunction { ): CallableFunction {
val functionDefinition = JavaFunctionDefinition.parse(definitions) val functionDefinition = JavaFunctionDefinition.parse(definitions)
val javaClass = lookupClass(functionDefinition.type) val javaClass = lookupClass(functionDefinition.type)

View File

@ -4,5 +4,5 @@ import gay.pizza.pork.tokenizer.CharSource
interface ContentSource { interface ContentSource {
fun loadAsCharSource(path: String): CharSource fun loadAsCharSource(path: String): CharSource
fun stableContentIdentity(path: String): String fun stableContentPath(path: String): String
} }

View File

@ -1,7 +1,7 @@
package gay.pizza.pork.frontend package gay.pizza.pork.frontend
class DynamicImportSource : ImportSource { class DynamicImportSource : ImportSource {
private val providers = mutableMapOf<String,ContentSource>() private val providers = mutableMapOf<String, ContentSource>()
override fun provideContentSource(form: String): ContentSource { override fun provideContentSource(form: String): ContentSource {
return providers[form] ?: return providers[form] ?:

View File

@ -10,7 +10,7 @@ class FsContentSource(val root: FsPath) : ContentSource {
override fun loadAsCharSource(path: String): CharSource = override fun loadAsCharSource(path: String): CharSource =
StringCharSource(asFsPath(path).readString()) StringCharSource(asFsPath(path).readString())
override fun stableContentIdentity(path: String): String = override fun stableContentPath(path: String): String =
asFsPath(path).fullPathString asFsPath(path).fullPathString
private fun asFsPath(path: String): FsPath { private fun asFsPath(path: String): FsPath {

View File

@ -0,0 +1,12 @@
package gay.pizza.pork.frontend
import gay.pizza.pork.ast.gen.CompilationUnit
import gay.pizza.pork.frontend.scope.SlabScope
class Slab(val world: World, val location: SourceLocation, val compilationUnit: CompilationUnit) {
val importedSlabs: List<Slab> by lazy {
world.resolveAllImports(this)
}
val scope: SlabScope by lazy { world.scope.index(this) }
}

View File

@ -0,0 +1,10 @@
package gay.pizza.pork.frontend
import gay.pizza.pork.tokenizer.SourceIndex
data class SourceLocation(val form: String, val filePath: String, val index: SourceIndex? = null) {
val commonFriendlyName: String by lazy { "$form $filePath" }
fun withSourceIndex(index: SourceIndex): SourceLocation =
SourceLocation(form, filePath, index)
}

View File

@ -0,0 +1,5 @@
package gay.pizza.pork.frontend
data class StableSourceKey(val form: String, val path: String) {
fun asSourceLocation(): SourceLocation = SourceLocation(form, path)
}

View File

@ -1,22 +1,25 @@
package gay.pizza.pork.frontend package gay.pizza.pork.frontend
import gay.pizza.pork.ast.gen.CompilationUnit
import gay.pizza.pork.ast.gen.ImportDeclaration import gay.pizza.pork.ast.gen.ImportDeclaration
import gay.pizza.pork.frontend.scope.WorldScope
import gay.pizza.pork.parser.DiscardNodeAttribution import gay.pizza.pork.parser.DiscardNodeAttribution
import gay.pizza.pork.parser.Parser import gay.pizza.pork.parser.Parser
import gay.pizza.pork.tokenizer.Tokenizer import gay.pizza.pork.tokenizer.Tokenizer
class World(val importSource: ImportSource) { class World(val importSource: ImportSource) {
private val internalUnits = mutableMapOf<String, CompilationUnit>() private val preludeImportLocator = ImportLocator("std", "lang/prelude.pork")
private val importedUnits = mutableMapOf<CompilationUnit, Set<CompilationUnit>>()
val units: List<CompilationUnit> private val internalSlabs = mutableMapOf<StableSourceKey, Slab>()
get() = internalUnits.values.toList()
private fun loadOneUnit(importLocator: ImportLocator): CompilationUnit { val slabs: List<Slab>
get() = internalSlabs.values.toList()
val scope: WorldScope by lazy { WorldScope(this) }
private fun loadOneSlab(importLocator: ImportLocator): Slab {
val contentSource = pickContentSource(importLocator.form) val contentSource = pickContentSource(importLocator.form)
val stableKey = stableIdentity(importLocator, contentSource = contentSource) val stableKey = stableSourceKey(importLocator, contentSource = contentSource)
val cached = internalUnits[stableKey] val cached = internalSlabs[stableKey]
if (cached != null) { if (cached != null) {
return cached return cached
} }
@ -24,40 +27,39 @@ class World(val importSource: ImportSource) {
val tokenizer = Tokenizer(charSource) val tokenizer = Tokenizer(charSource)
val parser = Parser(tokenizer, DiscardNodeAttribution) val parser = Parser(tokenizer, DiscardNodeAttribution)
val unit = parser.parseCompilationUnit() val unit = parser.parseCompilationUnit()
internalUnits[stableKey] = unit val slab = Slab(world = this, location = stableKey.asSourceLocation(), compilationUnit = unit)
return unit internalSlabs[stableKey] = slab
return slab
} }
private fun resolveAllImports(unit: CompilationUnit): Set<CompilationUnit> { internal fun resolveAllImports(slab: Slab): List<Slab> {
val units = mutableSetOf<CompilationUnit>() val slabs = mutableListOf<Slab>()
for (declaration in unit.declarations.filterIsInstance<ImportDeclaration>()) { if (slab.location.form != preludeImportLocator.form &&
slab.location.filePath != preludeImportLocator.path) {
slabs.add(loadOneSlab(preludeImportLocator))
}
for (declaration in slab.compilationUnit.declarations.filterIsInstance<ImportDeclaration>()) {
val importPath = declaration.path.components.joinToString("/") { it.id } + ".pork" val importPath = declaration.path.components.joinToString("/") { it.id } + ".pork"
val importLocator = ImportLocator(declaration.form.id, importPath) val importLocator = ImportLocator(declaration.form.id, importPath)
val importedUnit = loadOneUnit(importLocator) val importedModule = loadOneSlab(importLocator)
units.add(importedUnit) slabs.add(importedModule)
} }
importedUnits[unit] = units return slabs
return units
} }
fun load(importLocator: ImportLocator): CompilationUnit { fun load(importLocator: ImportLocator): Slab {
val unit = loadOneUnit(importLocator) return loadOneSlab(importLocator)
resolveAllImports(unit)
return unit
} }
fun importedBy(unit: CompilationUnit): Set<CompilationUnit> =
importedUnits[unit] ?: emptySet()
private fun pickContentSource(form: String): ContentSource = private fun pickContentSource(form: String): ContentSource =
importSource.provideContentSource(form) importSource.provideContentSource(form)
fun stableIdentity( fun stableSourceKey(
importLocator: ImportLocator, importLocator: ImportLocator,
contentSource: ContentSource = pickContentSource(importLocator.form) contentSource: ContentSource = pickContentSource(importLocator.form)
): String { ): StableSourceKey {
val formKey = importLocator.form val formKey = importLocator.form
val stableIdentity = contentSource.stableContentIdentity(importLocator.path) val stableContentPath = contentSource.stableContentPath(importLocator.path)
return "[${formKey}][${stableIdentity}]" return StableSourceKey(formKey, stableContentPath)
} }
} }

View File

@ -1,36 +0,0 @@
package gay.pizza.pork.frontend.scope
import gay.pizza.pork.ast.gen.CompilationUnit
import gay.pizza.pork.ast.gen.Symbol
class CompilationUnitScope(val worldScope: WorldScope, val unit: CompilationUnit) {
val externalSymbols = mutableSetOf<ScopeSymbol>()
val internalSymbols = mutableSetOf<ScopeSymbol>()
fun index() {
for (definition in unit.definitions) {
val scopeSymbol = ScopeSymbol(unit, definition)
if (definition.modifiers.export) {
externalSymbols.add(scopeSymbol)
}
internalSymbols.add(scopeSymbol)
}
}
fun findInternallyVisibleSymbols(): Set<VisibleScopeSymbol> {
val allSymbols = mutableMapOf<Symbol, VisibleScopeSymbol>()
val imports = worldScope.world.importedBy(unit)
for (import in imports) {
val scope = worldScope.index(import)
for (importedSymbol in scope.externalSymbols) {
allSymbols[importedSymbol.symbol] = VisibleScopeSymbol(unit, importedSymbol)
}
}
for (internalSymbol in internalSymbols) {
allSymbols[internalSymbol.symbol] = VisibleScopeSymbol(unit, internalSymbol)
}
return allSymbols.values.toSet()
}
}

View File

@ -0,0 +1,21 @@
package gay.pizza.pork.frontend.scope
import gay.pizza.pork.ast.gen.Definition
import gay.pizza.pork.ast.gen.Symbol
import gay.pizza.pork.ast.gen.visit
class DefinitionScope(val slabScope: SlabScope, val definition: Definition) {
val usedSymbols: List<ScopeSymbol> by lazy {
val symbols = mutableListOf<ScopeSymbol>()
val analyzer = ExternalSymbolUsageAnalyzer()
analyzer.visit(definition)
for (symbol in analyzer.usedSymbols) {
val resolved = slabScope.resolve(symbol)
?: throw RuntimeException("Unable to resolve symbol: ${symbol.id}")
symbols.add(resolved)
}
symbols
}
fun resolve(symbol: Symbol): ScopeSymbol? = slabScope.resolve(symbol)
}

View File

@ -0,0 +1,135 @@
package gay.pizza.pork.frontend.scope
import gay.pizza.pork.ast.FunctionLevelVisitor
import gay.pizza.pork.ast.gen.*
class ExternalSymbolUsageAnalyzer : FunctionLevelVisitor<Unit>() {
private val symbols = mutableSetOf<Symbol>()
private val internalSymbols = mutableListOf<MutableSet<Symbol>>()
val usedSymbols: Set<Symbol>
get() = symbols
override fun visitFunctionDefinition(node: FunctionDefinition) {
internalSymbols.add(node.arguments.map { it.symbol }.toMutableSet())
node.block?.visit(this)
internalSymbols.removeLast()
}
override fun visitLetDefinition(node: LetDefinition) {
node.value.visit(this)
}
override fun visitBlock(node: Block) {
internalSymbols.add(mutableSetOf())
node.visitChildren(this)
internalSymbols.removeLast()
}
override fun visitBooleanLiteral(node: BooleanLiteral) {
node.visitChildren(this)
}
override fun visitBreak(node: Break) {
node.visitChildren(this)
}
override fun visitContinue(node: Continue) {
node.visitChildren(this)
}
override fun visitDoubleLiteral(node: DoubleLiteral) {
node.visitChildren(this)
}
override fun visitForIn(node: ForIn) {
node.expression.visit(this)
internalSymbols.add(mutableSetOf(node.item.symbol))
node.block.visit(this)
internalSymbols.removeLast()
}
override fun visitFunctionCall(node: FunctionCall) {
checkAndContribute(node.symbol)
for (argument in node.arguments) {
visit(argument)
}
}
override fun visitIf(node: If) {
node.condition.visit(this)
node.thenBlock.visit(this)
node.elseBlock?.visit(this)
}
override fun visitIndexedBy(node: IndexedBy) {
node.visitChildren(this)
}
override fun visitInfixOperation(node: InfixOperation) {
node.visitChildren(this)
}
override fun visitIntegerLiteral(node: IntegerLiteral) {
node.visitChildren(this)
}
override fun visitLetAssignment(node: LetAssignment) {
internalSymbols.last().add(node.symbol)
node.value.visit(this)
}
override fun visitListLiteral(node: ListLiteral) {
node.visitChildren(this)
}
override fun visitLongLiteral(node: LongLiteral) {
node.visitChildren(this)
}
override fun visitNoneLiteral(node: NoneLiteral) {
node.visitChildren(this)
}
override fun visitParentheses(node: Parentheses) {
node.visitChildren(this)
}
override fun visitPrefixOperation(node: PrefixOperation) {
node.visitChildren(this)
}
override fun visitSetAssignment(node: SetAssignment) {
node.visitChildren(this)
}
override fun visitStringLiteral(node: StringLiteral) {
node.visitChildren(this)
}
override fun visitSuffixOperation(node: SuffixOperation) {
node.visitChildren(this)
}
override fun visitSymbolReference(node: SymbolReference) {
checkAndContribute(node.symbol)
}
override fun visitVarAssignment(node: VarAssignment) {
internalSymbols.last().add(node.symbol)
node.value.visit(this)
}
override fun visitWhile(node: While) {
node.condition.visit(this)
internalSymbols.add(mutableSetOf())
node.block.visit(this)
internalSymbols.removeLast()
}
private fun checkAndContribute(symbol: Symbol) {
if (internalSymbols.none { it.contains(symbol) }) {
symbols.add(symbol)
}
}
}

View File

@ -1,11 +1,9 @@
package gay.pizza.pork.frontend.scope package gay.pizza.pork.frontend.scope
import gay.pizza.pork.ast.gen.Definition import gay.pizza.pork.ast.gen.Definition
import gay.pizza.pork.ast.gen.Node import gay.pizza.pork.ast.gen.Symbol
class ScopeSymbol( class ScopeSymbol(val slabScope: SlabScope, val definition: Definition) {
val compilationUnit: Node, val symbol: Symbol = definition.symbol
val definition: Definition val scope: DefinitionScope by lazy { DefinitionScope(slabScope, definition) }
) {
val symbol = definition.symbol
} }

View File

@ -0,0 +1,48 @@
package gay.pizza.pork.frontend.scope
import gay.pizza.pork.ast.gen.Symbol
import gay.pizza.pork.frontend.Slab
class SlabScope(val worldScope: WorldScope, val slab: Slab) {
private val externalSymbolsList = mutableSetOf<ScopeSymbol>()
private val internalSymbolsList = mutableSetOf<ScopeSymbol>()
val externalSymbols: Set<ScopeSymbol>
get() = externalSymbolsList
val internalSymbols: Set<ScopeSymbol>
get() = internalSymbolsList
val internallyVisibleSymbols: List<VisibleScopeSymbol> by lazy { findInternallyVisibleSymbols() }
fun index() {
for (definition in slab.compilationUnit.definitions) {
val scopeSymbol = ScopeSymbol(this, definition)
if (definition.modifiers.export) {
externalSymbolsList.add(scopeSymbol)
}
internalSymbolsList.add(scopeSymbol)
}
}
private fun findInternallyVisibleSymbols(): List<VisibleScopeSymbol> {
val allSymbols = mutableMapOf<Symbol, VisibleScopeSymbol>()
val imports = slab.importedSlabs
for (import in imports) {
val scope = worldScope.index(import)
for (importedSymbol in scope.externalSymbols) {
allSymbols[importedSymbol.symbol] = VisibleScopeSymbol(slab, importedSymbol)
}
}
for (internalSymbol in internalSymbols) {
allSymbols[internalSymbol.symbol] = VisibleScopeSymbol(slab, internalSymbol)
}
return allSymbols.values.toList()
}
fun resolve(symbol: Symbol): ScopeSymbol? = internallyVisibleSymbols.firstOrNull {
it.scopeSymbol.symbol == symbol
}?.scopeSymbol
}

View File

@ -1,8 +1,8 @@
package gay.pizza.pork.frontend.scope package gay.pizza.pork.frontend.scope
import gay.pizza.pork.ast.gen.CompilationUnit import gay.pizza.pork.frontend.Slab
class VisibleScopeSymbol(val visibleToUnit: CompilationUnit, val scopeSymbol: ScopeSymbol) { class VisibleScopeSymbol(val visibleToSlab: Slab, val scopeSymbol: ScopeSymbol) {
val isInternalSymbol: Boolean val isInternalSymbol: Boolean
get() = visibleToUnit == scopeSymbol.compilationUnit get() = visibleToSlab == scopeSymbol.slabScope.slab
} }

View File

@ -1,24 +1,24 @@
package gay.pizza.pork.frontend.scope package gay.pizza.pork.frontend.scope
import gay.pizza.pork.ast.gen.CompilationUnit
import gay.pizza.pork.frontend.World import gay.pizza.pork.frontend.World
import gay.pizza.pork.frontend.Slab
class WorldScope(val world: World) { class WorldScope(val world: World) {
private val compilationUnitScopes = mutableMapOf<CompilationUnit, CompilationUnitScope>() private val slabScopes = mutableMapOf<Slab, SlabScope>()
fun indexAll() { fun indexAll() {
for (unit in world.units) { for (module in world.slabs) {
index(unit) index(module)
} }
} }
fun index(unit: CompilationUnit): CompilationUnitScope = fun index(slab: Slab): SlabScope =
scope(unit).apply { scope(slab).apply {
index() index()
} }
fun scope(unit: CompilationUnit): CompilationUnitScope = fun scope(slab: Slab): SlabScope =
compilationUnitScopes.computeIfAbsent(unit) { slabScopes.computeIfAbsent(slab) {
CompilationUnitScope(this, unit) SlabScope(this, slab)
} }
} }

View File

@ -36,16 +36,16 @@ abstract class Tool {
fun <T> visit(visitor: NodeVisitor<T>): T = visitor.visit(parse()) fun <T> visit(visitor: NodeVisitor<T>): T = visitor.visit(parse())
fun loadMainFunction(scope: Scope, setupEvaluator: Evaluator.() -> Unit = {}): CallableFunction { fun loadMainFunction(setupEvaluator: Evaluator.() -> Unit = {}): CallableFunction {
val world = buildWorld() val world = buildWorld()
val evaluator = Evaluator(world, scope) val evaluator = Evaluator(world)
setupEvaluator(evaluator) setupEvaluator(evaluator)
val resultingScope = evaluator.evaluate(rootImportLocator) val resultingScope = evaluator.evaluate(rootImportLocator)
return resultingScope.value("main") as CallableFunction return resultingScope.value("main") as CallableFunction
} }
fun loadMainFunctionStandard(scope: Scope, quiet: Boolean = false): CallableFunction = fun loadMainFunctionStandard(quiet: Boolean = false): CallableFunction =
loadMainFunction(scope, setupEvaluator = { loadMainFunction(setupEvaluator = {
addNativeProvider("internal", InternalNativeProvider(quiet = quiet)) addNativeProvider("internal", InternalNativeProvider(quiet = quiet))
addNativeProvider("ffi", FfiNativeProvider()) addNativeProvider("ffi", FfiNativeProvider())
addNativeProvider("java", JavaNativeProvider()) addNativeProvider("java", JavaNativeProvider())
@ -60,8 +60,8 @@ abstract class Tool {
return World(dynamicImportSource) return World(dynamicImportSource)
} }
fun run(scope: Scope, quiet: Boolean = false) { fun run(quiet: Boolean = false) {
val main = loadMainFunctionStandard(scope, quiet = quiet) val main = loadMainFunctionStandard(quiet = quiet)
main.call(emptyList(), CallStack()) main.call(emptyList(), CallStack())
} }
} }

View File

@ -11,5 +11,5 @@ fun main(args: Array<String>) {
} }
val path = PlatformFsProvider.resolve(args[0]) val path = PlatformFsProvider.resolve(args[0])
val tool = FileTool(path) val tool = FileTool(path)
tool.run(Scope.root()) tool.run()
} }

View File

@ -6,8 +6,12 @@ include(
":common", ":common",
":tokenizer", ":tokenizer",
":ast", ":ast",
":bytecode",
":parser", ":parser",
":frontend", ":frontend",
":compiler",
":execution",
":vm",
":evaluator", ":evaluator",
":stdlib", ":stdlib",
":ffi", ":ffi",

View File

@ -34,7 +34,7 @@ object PorkStdlib : ContentSource {
return StringCharSource(readPorkFile(path)) return StringCharSource(readPorkFile(path))
} }
override fun stableContentIdentity(path: String): String { override fun stableContentPath(path: String): String {
return path return path
} }
} }

View File

@ -1,7 +1,7 @@
export func ffiStructDefine(items...) export func ffiStructDefine(items...)
native ffi "internal" "ffiStructDefine" native ffi "internal" "ffiStructDefine"
export func ffiStructAllocate(struct) export func ffiStructAllocate(def)
native ffi "internal" "ffiStructAllocate" native ffi "internal" "ffiStructAllocate"
export func ffiStructValue(def, field, value) export func ffiStructValue(def, field, value)

View File

@ -76,7 +76,6 @@ enum class TokenType(vararg val properties: TokenTypeProperty) {
))), ))),
BlockComment(CharConsume(MatchedCharConsumer("/*", "*/")), CommentFamily), BlockComment(CharConsume(MatchedCharConsumer("/*", "*/")), CommentFamily),
LineComment(CharConsume(MatchedCharConsumer("//", "\n", AllowEofTermination)), CommentFamily), LineComment(CharConsume(MatchedCharConsumer("//", "\n", AllowEofTermination)), CommentFamily),
Struct(ManyChars("struct"), KeywordFamily),
EndOfFile; EndOfFile;
val promotions: List<Promotion> = val promotions: List<Promotion> =

View File

@ -7,6 +7,8 @@ plugins {
dependencies { dependencies {
api(project(":minimal")) api(project(":minimal"))
api(project(":compiler"))
api(project(":vm"))
api("com.github.ajalt.clikt:clikt:4.2.0") api("com.github.ajalt.clikt:clikt:4.2.0")
implementation(project(":common")) implementation(project(":common"))

View File

@ -0,0 +1,33 @@
package gay.pizza.pork.tool
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import gay.pizza.dough.fs.PlatformFsProvider
import gay.pizza.pork.ast.gen.Symbol
import gay.pizza.pork.compiler.Compiler
import gay.pizza.pork.minimal.FileTool
import gay.pizza.pork.vm.VirtualMachine
class CompileCommand : CliktCommand(help = "Compile Pork to Bytecode", name = "compile") {
val path by argument("file")
override fun run() {
val tool = FileTool(PlatformFsProvider.resolve(path))
val world = tool.buildWorld()
val compiler = Compiler()
val slab = world.load(tool.rootImportLocator)
val compiledSlab = compiler.compilableSlabs.of(slab)
val compiledMain = compiledSlab.compilableSymbolOf(Symbol("main"))
?: throw RuntimeException("'main' function not found.")
val compiledWorld = compiler.compile(compiledMain)
for (symbol in compiledWorld.symbolTable.symbols) {
val code = compiledWorld.code.subList(symbol.offset.toInt(), (symbol.offset + symbol.size).toInt())
println(symbol.id)
for ((index, op) in code.withIndex()) {
println(" ${symbol.offset + index.toUInt()} ${op.code.name} ${op.args.joinToString(" ")}")
}
}
val vm = VirtualMachine(compiledWorld)
vm.execute()
}
}

View File

@ -17,7 +17,8 @@ class RootCommand : CliktCommand(
AstCommand(), AstCommand(),
AttributeCommand(), AttributeCommand(),
ScopeAnalysisCommand(), ScopeAnalysisCommand(),
CopyStdlibCommand() CopyStdlibCommand(),
CompileCommand()
) )
} }

View File

@ -6,8 +6,6 @@ import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.options.option
import gay.pizza.dough.fs.PlatformFsProvider import gay.pizza.dough.fs.PlatformFsProvider
import gay.pizza.pork.evaluator.* import gay.pizza.pork.evaluator.*
import gay.pizza.pork.ffi.FfiNativeProvider
import gay.pizza.pork.ffi.JavaNativeProvider
import gay.pizza.pork.minimal.FileTool import gay.pizza.pork.minimal.FileTool
class RunCommand : CliktCommand(help = "Run Program", name = "run") { class RunCommand : CliktCommand(help = "Run Program", name = "run") {
@ -19,12 +17,11 @@ class RunCommand : CliktCommand(help = "Run Program", name = "run") {
override fun run() { override fun run() {
val tool = FileTool(PlatformFsProvider.resolve(path)) val tool = FileTool(PlatformFsProvider.resolve(path))
val scope = Scope.root() val main = tool.loadMainFunctionStandard(quiet = quiet)
val main = tool.loadMainFunctionStandard(scope, quiet = quiet)
if (dumpScope) { if (dumpScope) {
val functionContext = main as FunctionContext val functionContext = main as FunctionContext
val internalScope = functionContext.compilationUnitContext.internalScope val internalScope = functionContext.slabContext.internalScope
internalScope.crawlScopePath { key, path -> internalScope.crawlScopePath { key, path ->
println("[scope] $key [${path.joinToString(" -> ")}]") println("[scope] $key [${path.joinToString(" -> ")}]")
} }

View File

@ -15,12 +15,12 @@ class ScopeAnalysisCommand : CliktCommand(help = "Run Scope Analysis", name = "s
val root = world.load(tool.rootImportLocator) val root = world.load(tool.rootImportLocator)
val scope = WorldScope(world).apply { index(root) } val scope = WorldScope(world).apply { index(root) }
val rootScope = scope.scope(root) val rootScope = scope.scope(root)
val visibleScopeSymbols = rootScope.findInternallyVisibleSymbols() for (visibleScopeSymbol in rootScope.internallyVisibleSymbols) {
for (visibleScopeSymbol in visibleScopeSymbols) {
println( println(
"symbol ${visibleScopeSymbol.scopeSymbol.symbol.id} " + "symbol ${visibleScopeSymbol.scopeSymbol.symbol.id} " +
"type=${visibleScopeSymbol.scopeSymbol.definition.type.name} " + "type=${visibleScopeSymbol.scopeSymbol.definition.type.name} " +
"internal=${visibleScopeSymbol.isInternalSymbol}" "internal=${visibleScopeSymbol.isInternalSymbol} " +
"slab=${visibleScopeSymbol.scopeSymbol.slab.location.commonFriendlyName}"
) )
} }
} }

11
vm/build.gradle.kts Normal file
View File

@ -0,0 +1,11 @@
plugins {
id("gay.pizza.pork.module")
}
dependencies {
api(project(":execution"))
api(project(":bytecode"))
api(project(":compiler"))
implementation(project(":common"))
}

View File

@ -0,0 +1,80 @@
package gay.pizza.pork.vm
import gay.pizza.pork.bytecode.CompiledWorld
class InternalMachine(val world: CompiledWorld, val handlers: List<OpHandler>) {
private val inlined = world.code.map { op ->
val handler = handlers.firstOrNull { it.code == op.code } ?:
throw VirtualMachineException("Opcode ${op.code.name} does not have a handler.")
op to handler
}
private var inst: UInt = 0u
private val stack = mutableListOf<Any>(EndOfCode)
private val locals = mutableListOf<MutableMap<UInt, Any>>(
mutableMapOf()
)
private var autoNextInst = true
private var exitFlag = false
fun step(): Boolean {
val (op, handler) = inlined[inst.toInt()]
handler.handle(this, op)
if (autoNextInst) {
inst++
}
autoNextInst = true
return !exitFlag
}
fun pushScope() {
locals.add(mutableMapOf())
}
fun popScope() {
locals.removeLast()
}
fun loadConstant(id: UInt) {
push(world.constantPool.constants[id.toInt()])
}
fun loadLocal(id: UInt) {
val localSet = locals.last()
val local = localSet[id]
?: throw VirtualMachineException("Attempted to load local $id but it was not stored.")
push(local)
}
fun storeLocal(id: UInt) {
val localSet = locals.last()
val value = pop()
localSet[id] = value
}
fun setNextInst(value: UInt) {
inst = value
autoNextInst = false
}
fun push(item: Any) {
stack.add(item)
}
fun pop(): Any = stack.removeLast()
fun exit() {
exitFlag = true
}
fun reset() {
stack.clear()
stack.add(EndOfCode)
locals.clear()
locals.add(mutableMapOf())
inst = 0u
exitFlag = false
autoNextInst = true
}
data object EndOfCode
}

View File

@ -0,0 +1,8 @@
package gay.pizza.pork.vm
import gay.pizza.pork.bytecode.Op
import gay.pizza.pork.bytecode.Opcode
abstract class OpHandler(val code: Opcode) {
abstract fun handle(machine: InternalMachine, op: Op)
}

View File

@ -0,0 +1,45 @@
package gay.pizza.pork.vm
import gay.pizza.pork.bytecode.CompiledWorld
import gay.pizza.pork.execution.ExecutionContext
import gay.pizza.pork.vm.ops.*
class VirtualMachine(world: CompiledWorld) : ExecutionContext {
private val internal = InternalMachine(world, listOf(
IntegerOpHandler,
ConstantOpHandler,
TrueOpHandler,
FalseOpHandler,
ListOpHandler,
CompareEqualOpHandler,
CompareLesserEqualOpHandler,
AddOpHandler,
JumpOpHandler,
JumpIfOpHandler,
LoadLocalOpHandler,
StoreLocalOpHandler,
CallOpHandler,
RetOpHandler,
NativeOpHandler,
ScopeInOpHandler,
ScopeOutOpHandler
))
override fun execute() {
while (true) {
if (!internal.step()) {
break
}
}
internal.reset()
}
}

View File

@ -0,0 +1,3 @@
package gay.pizza.pork.vm
class VirtualMachineException(message: String) : RuntimeException(message)

View File

@ -0,0 +1,20 @@
package gay.pizza.pork.vm
import gay.pizza.pork.ast.gen.Symbol
import gay.pizza.pork.compiler.Compiler
import gay.pizza.pork.execution.ExecutionContext
import gay.pizza.pork.execution.ExecutionContextProvider
import gay.pizza.pork.frontend.ImportLocator
import gay.pizza.pork.frontend.World
class VirtualMachineProvider(val world: World) : ExecutionContextProvider {
override fun prepare(importLocator: ImportLocator, entryPointSymbol: Symbol): ExecutionContext {
val compiler = Compiler()
val slab = world.load(importLocator)
val compilableSlab = compiler.compilableSlabs.of(slab)
val compilableSymbol = compilableSlab.compilableSymbolOf(entryPointSymbol) ?:
throw RuntimeException("Unable to find compilable symbol for entry point '${entryPointSymbol.id}'")
val compiledWorld = compiler.compile(compilableSymbol)
return VirtualMachine(compiledWorld)
}
}

View File

@ -0,0 +1,19 @@
package gay.pizza.pork.vm.ops
import gay.pizza.pork.bytecode.Op
import gay.pizza.pork.bytecode.Opcode
import gay.pizza.pork.vm.InternalMachine
import gay.pizza.pork.vm.OpHandler
import gay.pizza.pork.vm.VirtualMachineException
object AddOpHandler : OpHandler(Opcode.Add) {
override fun handle(machine: InternalMachine, op: Op) {
val left = machine.pop()
val right = machine.pop()
if (left !is Int || right !is Int) {
throw VirtualMachineException("Bad types.")
}
machine.push(left + right)
}
}

View File

@ -0,0 +1,13 @@
package gay.pizza.pork.vm.ops
import gay.pizza.pork.bytecode.Op
import gay.pizza.pork.bytecode.Opcode
import gay.pizza.pork.vm.InternalMachine
import gay.pizza.pork.vm.OpHandler
object CallOpHandler : OpHandler(Opcode.Call) {
override fun handle(machine: InternalMachine, op: Op) {
machine.setNextInst(op.args[0])
machine.pushScope()
}
}

View File

@ -0,0 +1,12 @@
package gay.pizza.pork.vm.ops
import gay.pizza.pork.bytecode.Op
import gay.pizza.pork.bytecode.Opcode
import gay.pizza.pork.vm.InternalMachine
import gay.pizza.pork.vm.OpHandler
object CompareEqualOpHandler : OpHandler(Opcode.CompareEqual) {
override fun handle(machine: InternalMachine, op: Op) {
machine.push(machine.pop() == machine.pop())
}
}

View File

@ -0,0 +1,19 @@
package gay.pizza.pork.vm.ops
import gay.pizza.pork.bytecode.Op
import gay.pizza.pork.bytecode.Opcode
import gay.pizza.pork.vm.InternalMachine
import gay.pizza.pork.vm.OpHandler
import gay.pizza.pork.vm.VirtualMachineException
object CompareLesserEqualOpHandler : OpHandler(Opcode.CompareLesserEqual) {
override fun handle(machine: InternalMachine, op: Op) {
val right = machine.pop()
val left = machine.pop()
if (left !is Int || right !is Int) {
throw VirtualMachineException("Bad types.")
}
machine.push(left <= right)
}
}

View File

@ -0,0 +1,12 @@
package gay.pizza.pork.vm.ops
import gay.pizza.pork.bytecode.Op
import gay.pizza.pork.bytecode.Opcode
import gay.pizza.pork.vm.InternalMachine
import gay.pizza.pork.vm.OpHandler
object ConstantOpHandler : OpHandler(Opcode.Constant) {
override fun handle(machine: InternalMachine, op: Op) {
machine.loadConstant(op.args[0])
}
}

View File

@ -0,0 +1,12 @@
package gay.pizza.pork.vm.ops
import gay.pizza.pork.bytecode.Op
import gay.pizza.pork.bytecode.Opcode
import gay.pizza.pork.vm.InternalMachine
import gay.pizza.pork.vm.OpHandler
object FalseOpHandler : OpHandler(Opcode.False) {
override fun handle(machine: InternalMachine, op: Op) {
machine.push(false)
}
}

View File

@ -0,0 +1,12 @@
package gay.pizza.pork.vm.ops
import gay.pizza.pork.bytecode.Op
import gay.pizza.pork.bytecode.Opcode
import gay.pizza.pork.vm.InternalMachine
import gay.pizza.pork.vm.OpHandler
object IntegerOpHandler : OpHandler(Opcode.Integer) {
override fun handle(machine: InternalMachine, op: Op) {
machine.push(op.args[0].toInt())
}
}

View File

@ -0,0 +1,20 @@
package gay.pizza.pork.vm.ops
import gay.pizza.pork.bytecode.Op
import gay.pizza.pork.bytecode.Opcode
import gay.pizza.pork.vm.InternalMachine
import gay.pizza.pork.vm.OpHandler
import gay.pizza.pork.vm.VirtualMachineException
object JumpIfOpHandler : OpHandler(Opcode.JumpIf) {
override fun handle(machine: InternalMachine, op: Op) {
val value = machine.pop()
if (value !is Boolean) {
throw VirtualMachineException("JumpIf expects a boolean value on the stack.")
}
if (value) {
machine.setNextInst(op.args[0])
}
}
}

View File

@ -0,0 +1,12 @@
package gay.pizza.pork.vm.ops
import gay.pizza.pork.bytecode.Op
import gay.pizza.pork.bytecode.Opcode
import gay.pizza.pork.vm.InternalMachine
import gay.pizza.pork.vm.OpHandler
object JumpOpHandler : OpHandler(Opcode.Jump) {
override fun handle(machine: InternalMachine, op: Op) {
machine.setNextInst(op.args[0])
}
}

View File

@ -0,0 +1,18 @@
package gay.pizza.pork.vm.ops
import gay.pizza.pork.bytecode.Op
import gay.pizza.pork.bytecode.Opcode
import gay.pizza.pork.vm.InternalMachine
import gay.pizza.pork.vm.OpHandler
object ListOpHandler : OpHandler(Opcode.List) {
override fun handle(machine: InternalMachine, op: Op) {
val count = op.args[0]
val list = mutableListOf<Any>()
for (i in 1u..count) {
val item = machine.pop()
list.add(item)
}
machine.push(list.reversed())
}
}

View File

@ -0,0 +1,12 @@
package gay.pizza.pork.vm.ops
import gay.pizza.pork.bytecode.Op
import gay.pizza.pork.bytecode.Opcode
import gay.pizza.pork.vm.InternalMachine
import gay.pizza.pork.vm.OpHandler
object LoadLocalOpHandler : OpHandler(Opcode.LoadLocal) {
override fun handle(machine: InternalMachine, op: Op) {
machine.loadLocal(op.args[0])
}
}

View File

@ -0,0 +1,16 @@
package gay.pizza.pork.vm.ops
import gay.pizza.pork.bytecode.Op
import gay.pizza.pork.bytecode.Opcode
import gay.pizza.pork.vm.InternalMachine
import gay.pizza.pork.vm.OpHandler
object NativeOpHandler : OpHandler(Opcode.Native) {
override fun handle(machine: InternalMachine, op: Op) {
val countOfNativeDefs = op.args[1].toInt()
val defs = mutableListOf<Any>()
for (i in 0 until countOfNativeDefs) {
defs.add(String(machine.pop() as ByteArray))
}
}
}

View File

@ -0,0 +1,18 @@
package gay.pizza.pork.vm.ops
import gay.pizza.pork.bytecode.Op
import gay.pizza.pork.bytecode.Opcode
import gay.pizza.pork.vm.InternalMachine
import gay.pizza.pork.vm.OpHandler
object RetOpHandler : OpHandler(Opcode.Return) {
override fun handle(machine: InternalMachine, op: Op) {
val last = machine.pop()
if (last == InternalMachine.EndOfCode) {
machine.exit()
return
}
machine.popScope()
machine.setNextInst((last as Int).toUInt())
}
}

View File

@ -0,0 +1,10 @@
package gay.pizza.pork.vm.ops
import gay.pizza.pork.bytecode.Op
import gay.pizza.pork.bytecode.Opcode
import gay.pizza.pork.vm.InternalMachine
import gay.pizza.pork.vm.OpHandler
object ScopeInOpHandler : OpHandler(Opcode.ScopeIn) {
override fun handle(machine: InternalMachine, op: Op) {}
}

View File

@ -0,0 +1,10 @@
package gay.pizza.pork.vm.ops
import gay.pizza.pork.bytecode.Op
import gay.pizza.pork.bytecode.Opcode
import gay.pizza.pork.vm.InternalMachine
import gay.pizza.pork.vm.OpHandler
object ScopeOutOpHandler : OpHandler(Opcode.ScopeOut) {
override fun handle(machine: InternalMachine, op: Op) {}
}

View File

@ -0,0 +1,12 @@
package gay.pizza.pork.vm.ops
import gay.pizza.pork.bytecode.Op
import gay.pizza.pork.bytecode.Opcode
import gay.pizza.pork.vm.InternalMachine
import gay.pizza.pork.vm.OpHandler
object StoreLocalOpHandler : OpHandler(Opcode.StoreLocal) {
override fun handle(machine: InternalMachine, op: Op) {
machine.storeLocal(op.args[0])
}
}

View File

@ -0,0 +1,12 @@
package gay.pizza.pork.vm.ops
import gay.pizza.pork.bytecode.Op
import gay.pizza.pork.bytecode.Opcode
import gay.pizza.pork.vm.InternalMachine
import gay.pizza.pork.vm.OpHandler
object TrueOpHandler : OpHandler(Opcode.True) {
override fun handle(machine: InternalMachine, op: Op) {
machine.push(true)
}
}