From 041848c14e8e8963973c1423f80c5040a53338b0 Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Tue, 14 Nov 2023 23:44:10 -0800 Subject: [PATCH] vm: very basic virtual machine --- .../pizza/pork/ast/FunctionLevelVisitor.kt | 42 +++ bytecode/build.gradle.kts | 7 + .../gay/pizza/pork/bytecode/CompiledWorld.kt | 10 + .../gay/pizza/pork/bytecode/Constant.kt | 3 + .../gay/pizza/pork/bytecode/ConstantPool.kt | 6 + .../pork/bytecode/MutableConstantPool.kt | 18 + .../gay/pizza/pork/bytecode/MutableRel.kt | 3 + .../main/kotlin/gay/pizza/pork/bytecode/Op.kt | 6 + .../kotlin/gay/pizza/pork/bytecode/Opcode.kt | 43 +++ .../kotlin/gay/pizza/pork/bytecode/Ops.kt | 3 + .../gay/pizza/pork/bytecode/SymbolInfo.kt | 10 + .../gay/pizza/pork/bytecode/SymbolTable.kt | 8 + .../pizza/pork/common/ByteRepresentation.kt | 40 +++ compiler/build.gradle.kts | 11 + .../gay/pizza/pork/compiler/CompilableSlab.kt | 16 + .../pizza/pork/compiler/CompilableSymbol.kt | 34 ++ .../pork/compiler/CompiledWorldLayout.kt | 38 ++ .../gay/pizza/pork/compiler/Compiler.kt | 47 +++ .../pizza/pork/compiler/ComputableState.kt | 7 + .../gay/pizza/pork/compiler/LoopState.kt | 11 + .../gay/pizza/pork/compiler/PatchRelOp.kt | 12 + .../gay/pizza/pork/compiler/PatchSymOp.kt | 13 + .../gay/pizza/pork/compiler/StaticOp.kt | 7 + .../gay/pizza/pork/compiler/StoredState.kt | 11 + .../kotlin/gay/pizza/pork/compiler/StubOp.kt | 7 + .../gay/pizza/pork/compiler/StubOpEmitter.kt | 336 ++++++++++++++++++ .../pork/compiler/StubResolutionContext.kt | 5 + .../kotlin/gay/pizza/pork/compiler/StubVar.kt | 8 + evaluator/build.gradle.kts | 1 + .../pork/evaluator/CompilationUnitContext.kt | 78 ---- .../pizza/pork/evaluator/EvaluationVisitor.kt | 34 +- .../gay/pizza/pork/evaluator/Evaluator.kt | 40 ++- .../evaluator/EvaluatorExecutionContext.kt | 18 + .../pizza/pork/evaluator/FunctionContext.kt | 10 +- .../pork/evaluator/InternalNativeProvider.kt | 2 +- .../pizza/pork/evaluator/NativeProvider.kt | 2 +- .../gay/pizza/pork/evaluator/SlabContext.kt | 62 ++++ examples/count.pork | 10 +- execution/build.gradle.kts | 8 + .../pizza/pork/execution/ExecutionContext.kt | 5 + .../execution/ExecutionContextProvider.kt | 8 + .../gay/pizza/pork/ffi/FfiNativeProvider.kt | 4 +- .../pork/ffi/JavaAutogenContentSource.kt | 2 +- .../gay/pizza/pork/ffi/JavaNativeProvider.kt | 4 +- .../gay/pizza/pork/frontend/ContentSource.kt | 2 +- .../pork/frontend/DynamicImportSource.kt | 2 +- .../pizza/pork/frontend/FsContentSource.kt | 2 +- .../kotlin/gay/pizza/pork/frontend/Slab.kt | 12 + .../gay/pizza/pork/frontend/SourceLocation.kt | 10 + .../pizza/pork/frontend/StableSourceKey.kt | 5 + .../kotlin/gay/pizza/pork/frontend/World.kt | 58 +-- .../frontend/scope/CompilationUnitScope.kt | 36 -- .../pork/frontend/scope/DefinitionScope.kt | 21 ++ .../scope/ExternalSymbolUsageAnalyzer.kt | 135 +++++++ .../pizza/pork/frontend/scope/ScopeSymbol.kt | 10 +- .../pizza/pork/frontend/scope/SlabScope.kt | 48 +++ .../pork/frontend/scope/VisibleScopeSymbol.kt | 6 +- .../pizza/pork/frontend/scope/WorldScope.kt | 18 +- .../kotlin/gay/pizza/pork/minimal/Tool.kt | 12 +- .../kotlin/gay/pizza/pork/minimal/main.kt | 2 +- settings.gradle.kts | 4 + .../gay/pizza/pork/stdlib/PorkStdlib.kt | 2 +- stdlib/src/main/pork/ffi/struct.pork | 2 +- .../gay/pizza/pork/tokenizer/TokenType.kt | 1 - tool/build.gradle.kts | 2 + .../gay/pizza/pork/tool/CompileCommand.kt | 33 ++ .../kotlin/gay/pizza/pork/tool/RootCommand.kt | 3 +- .../kotlin/gay/pizza/pork/tool/RunCommand.kt | 7 +- .../pizza/pork/tool/ScopeAnalysisCommand.kt | 6 +- vm/build.gradle.kts | 11 + .../gay/pizza/pork/vm/InternalMachine.kt | 80 +++++ .../kotlin/gay/pizza/pork/vm/OpHandler.kt | 8 + .../gay/pizza/pork/vm/VirtualMachine.kt | 45 +++ .../pizza/pork/vm/VirtualMachineException.kt | 3 + .../pizza/pork/vm/VirtualMachineProvider.kt | 20 ++ .../gay/pizza/pork/vm/ops/AddOpHandler.kt | 19 + .../gay/pizza/pork/vm/ops/CallOpHandler.kt | 13 + .../pork/vm/ops/CompareEqualOpHandler.kt | 12 + .../vm/ops/CompareLesserEqualOpHandler.kt | 19 + .../pizza/pork/vm/ops/ConstantOpHandler.kt | 12 + .../gay/pizza/pork/vm/ops/FalseOpHandler.kt | 12 + .../gay/pizza/pork/vm/ops/IntegerOpHandler.kt | 12 + .../gay/pizza/pork/vm/ops/JumpIfOpHandler.kt | 20 ++ .../gay/pizza/pork/vm/ops/JumpOpHandler.kt | 12 + .../gay/pizza/pork/vm/ops/ListOpHandler.kt | 18 + .../pizza/pork/vm/ops/LoadLocalOpHandler.kt | 12 + .../gay/pizza/pork/vm/ops/NativeOpHandler.kt | 16 + .../gay/pizza/pork/vm/ops/RetOpHandler.kt | 18 + .../gay/pizza/pork/vm/ops/ScopeInOpHandler.kt | 10 + .../pizza/pork/vm/ops/ScopeOutOpHandler.kt | 10 + .../pizza/pork/vm/ops/StoreLocalOpHandler.kt | 12 + .../gay/pizza/pork/vm/ops/TrueOpHandler.kt | 12 + 92 files changed, 1652 insertions(+), 243 deletions(-) create mode 100644 ast/src/main/kotlin/gay/pizza/pork/ast/FunctionLevelVisitor.kt create mode 100644 bytecode/build.gradle.kts create mode 100644 bytecode/src/main/kotlin/gay/pizza/pork/bytecode/CompiledWorld.kt create mode 100644 bytecode/src/main/kotlin/gay/pizza/pork/bytecode/Constant.kt create mode 100644 bytecode/src/main/kotlin/gay/pizza/pork/bytecode/ConstantPool.kt create mode 100644 bytecode/src/main/kotlin/gay/pizza/pork/bytecode/MutableConstantPool.kt create mode 100644 bytecode/src/main/kotlin/gay/pizza/pork/bytecode/MutableRel.kt create mode 100644 bytecode/src/main/kotlin/gay/pizza/pork/bytecode/Op.kt create mode 100644 bytecode/src/main/kotlin/gay/pizza/pork/bytecode/Opcode.kt create mode 100644 bytecode/src/main/kotlin/gay/pizza/pork/bytecode/Ops.kt create mode 100644 bytecode/src/main/kotlin/gay/pizza/pork/bytecode/SymbolInfo.kt create mode 100644 bytecode/src/main/kotlin/gay/pizza/pork/bytecode/SymbolTable.kt create mode 100644 common/src/main/kotlin/gay/pizza/pork/common/ByteRepresentation.kt create mode 100644 compiler/build.gradle.kts create mode 100644 compiler/src/main/kotlin/gay/pizza/pork/compiler/CompilableSlab.kt create mode 100644 compiler/src/main/kotlin/gay/pizza/pork/compiler/CompilableSymbol.kt create mode 100644 compiler/src/main/kotlin/gay/pizza/pork/compiler/CompiledWorldLayout.kt create mode 100644 compiler/src/main/kotlin/gay/pizza/pork/compiler/Compiler.kt create mode 100644 compiler/src/main/kotlin/gay/pizza/pork/compiler/ComputableState.kt create mode 100644 compiler/src/main/kotlin/gay/pizza/pork/compiler/LoopState.kt create mode 100644 compiler/src/main/kotlin/gay/pizza/pork/compiler/PatchRelOp.kt create mode 100644 compiler/src/main/kotlin/gay/pizza/pork/compiler/PatchSymOp.kt create mode 100644 compiler/src/main/kotlin/gay/pizza/pork/compiler/StaticOp.kt create mode 100644 compiler/src/main/kotlin/gay/pizza/pork/compiler/StoredState.kt create mode 100644 compiler/src/main/kotlin/gay/pizza/pork/compiler/StubOp.kt create mode 100644 compiler/src/main/kotlin/gay/pizza/pork/compiler/StubOpEmitter.kt create mode 100644 compiler/src/main/kotlin/gay/pizza/pork/compiler/StubResolutionContext.kt create mode 100644 compiler/src/main/kotlin/gay/pizza/pork/compiler/StubVar.kt delete mode 100644 evaluator/src/main/kotlin/gay/pizza/pork/evaluator/CompilationUnitContext.kt create mode 100644 evaluator/src/main/kotlin/gay/pizza/pork/evaluator/EvaluatorExecutionContext.kt create mode 100644 evaluator/src/main/kotlin/gay/pizza/pork/evaluator/SlabContext.kt create mode 100644 execution/build.gradle.kts create mode 100644 execution/src/main/kotlin/gay/pizza/pork/execution/ExecutionContext.kt create mode 100644 execution/src/main/kotlin/gay/pizza/pork/execution/ExecutionContextProvider.kt create mode 100644 frontend/src/main/kotlin/gay/pizza/pork/frontend/Slab.kt create mode 100644 frontend/src/main/kotlin/gay/pizza/pork/frontend/SourceLocation.kt create mode 100644 frontend/src/main/kotlin/gay/pizza/pork/frontend/StableSourceKey.kt delete mode 100644 frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/CompilationUnitScope.kt create mode 100644 frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/DefinitionScope.kt create mode 100644 frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/ExternalSymbolUsageAnalyzer.kt create mode 100644 frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/SlabScope.kt create mode 100644 tool/src/main/kotlin/gay/pizza/pork/tool/CompileCommand.kt create mode 100644 vm/build.gradle.kts create mode 100644 vm/src/main/kotlin/gay/pizza/pork/vm/InternalMachine.kt create mode 100644 vm/src/main/kotlin/gay/pizza/pork/vm/OpHandler.kt create mode 100644 vm/src/main/kotlin/gay/pizza/pork/vm/VirtualMachine.kt create mode 100644 vm/src/main/kotlin/gay/pizza/pork/vm/VirtualMachineException.kt create mode 100644 vm/src/main/kotlin/gay/pizza/pork/vm/VirtualMachineProvider.kt create mode 100644 vm/src/main/kotlin/gay/pizza/pork/vm/ops/AddOpHandler.kt create mode 100644 vm/src/main/kotlin/gay/pizza/pork/vm/ops/CallOpHandler.kt create mode 100644 vm/src/main/kotlin/gay/pizza/pork/vm/ops/CompareEqualOpHandler.kt create mode 100644 vm/src/main/kotlin/gay/pizza/pork/vm/ops/CompareLesserEqualOpHandler.kt create mode 100644 vm/src/main/kotlin/gay/pizza/pork/vm/ops/ConstantOpHandler.kt create mode 100644 vm/src/main/kotlin/gay/pizza/pork/vm/ops/FalseOpHandler.kt create mode 100644 vm/src/main/kotlin/gay/pizza/pork/vm/ops/IntegerOpHandler.kt create mode 100644 vm/src/main/kotlin/gay/pizza/pork/vm/ops/JumpIfOpHandler.kt create mode 100644 vm/src/main/kotlin/gay/pizza/pork/vm/ops/JumpOpHandler.kt create mode 100644 vm/src/main/kotlin/gay/pizza/pork/vm/ops/ListOpHandler.kt create mode 100644 vm/src/main/kotlin/gay/pizza/pork/vm/ops/LoadLocalOpHandler.kt create mode 100644 vm/src/main/kotlin/gay/pizza/pork/vm/ops/NativeOpHandler.kt create mode 100644 vm/src/main/kotlin/gay/pizza/pork/vm/ops/RetOpHandler.kt create mode 100644 vm/src/main/kotlin/gay/pizza/pork/vm/ops/ScopeInOpHandler.kt create mode 100644 vm/src/main/kotlin/gay/pizza/pork/vm/ops/ScopeOutOpHandler.kt create mode 100644 vm/src/main/kotlin/gay/pizza/pork/vm/ops/StoreLocalOpHandler.kt create mode 100644 vm/src/main/kotlin/gay/pizza/pork/vm/ops/TrueOpHandler.kt diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/FunctionLevelVisitor.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/FunctionLevelVisitor.kt new file mode 100644 index 0000000..9695277 --- /dev/null +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/FunctionLevelVisitor.kt @@ -0,0 +1,42 @@ +package gay.pizza.pork.ast + +import gay.pizza.pork.ast.gen.* + +abstract class FunctionLevelVisitor : NodeVisitor { + 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.") + } +} diff --git a/bytecode/build.gradle.kts b/bytecode/build.gradle.kts new file mode 100644 index 0000000..55f1ba8 --- /dev/null +++ b/bytecode/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + id("gay.pizza.pork.module") +} + +dependencies { + implementation(project(":common")) +} diff --git a/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/CompiledWorld.kt b/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/CompiledWorld.kt new file mode 100644 index 0000000..18221a6 --- /dev/null +++ b/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/CompiledWorld.kt @@ -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 +) diff --git a/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/Constant.kt b/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/Constant.kt new file mode 100644 index 0000000..1bcfc13 --- /dev/null +++ b/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/Constant.kt @@ -0,0 +1,3 @@ +package gay.pizza.pork.bytecode + +class Constant(val id: UInt, val value: ByteArray) diff --git a/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/ConstantPool.kt b/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/ConstantPool.kt new file mode 100644 index 0000000..09c69a2 --- /dev/null +++ b/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/ConstantPool.kt @@ -0,0 +1,6 @@ +package gay.pizza.pork.bytecode + +import kotlinx.serialization.Serializable + +@Serializable +data class ConstantPool(val constants: List) diff --git a/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/MutableConstantPool.kt b/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/MutableConstantPool.kt new file mode 100644 index 0000000..80c7d2f --- /dev/null +++ b/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/MutableConstantPool.kt @@ -0,0 +1,18 @@ +package gay.pizza.pork.bytecode + +class MutableConstantPool { + private val pool = mutableListOf() + + 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 = pool +} diff --git a/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/MutableRel.kt b/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/MutableRel.kt new file mode 100644 index 0000000..758c58b --- /dev/null +++ b/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/MutableRel.kt @@ -0,0 +1,3 @@ +package gay.pizza.pork.bytecode + +class MutableRel(var rel: UInt) diff --git a/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/Op.kt b/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/Op.kt new file mode 100644 index 0000000..1779659 --- /dev/null +++ b/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/Op.kt @@ -0,0 +1,6 @@ +package gay.pizza.pork.bytecode + +import kotlinx.serialization.Serializable + +@Serializable +data class Op(val code: Opcode, val args: List) diff --git a/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/Opcode.kt b/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/Opcode.kt new file mode 100644 index 0000000..f466186 --- /dev/null +++ b/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/Opcode.kt @@ -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), +} diff --git a/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/Ops.kt b/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/Ops.kt new file mode 100644 index 0000000..0ba222b --- /dev/null +++ b/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/Ops.kt @@ -0,0 +1,3 @@ +package gay.pizza.pork.bytecode + +class Ops(val ops: List) diff --git a/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/SymbolInfo.kt b/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/SymbolInfo.kt new file mode 100644 index 0000000..5ce0895 --- /dev/null +++ b/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/SymbolInfo.kt @@ -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 +) diff --git a/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/SymbolTable.kt b/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/SymbolTable.kt new file mode 100644 index 0000000..ba54398 --- /dev/null +++ b/bytecode/src/main/kotlin/gay/pizza/pork/bytecode/SymbolTable.kt @@ -0,0 +1,8 @@ +package gay.pizza.pork.bytecode + +import kotlinx.serialization.Serializable + +@Serializable +data class SymbolTable( + val symbols: List +) diff --git a/common/src/main/kotlin/gay/pizza/pork/common/ByteRepresentation.kt b/common/src/main/kotlin/gay/pizza/pork/common/ByteRepresentation.kt new file mode 100644 index 0000000..dff7426 --- /dev/null +++ b/common/src/main/kotlin/gay/pizza/pork/common/ByteRepresentation.kt @@ -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) + } +} diff --git a/compiler/build.gradle.kts b/compiler/build.gradle.kts new file mode 100644 index 0000000..fbb65c8 --- /dev/null +++ b/compiler/build.gradle.kts @@ -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")) +} diff --git a/compiler/src/main/kotlin/gay/pizza/pork/compiler/CompilableSlab.kt b/compiler/src/main/kotlin/gay/pizza/pork/compiler/CompilableSlab.kt new file mode 100644 index 0000000..609371b --- /dev/null +++ b/compiler/src/main/kotlin/gay/pizza/pork/compiler/CompilableSlab.kt @@ -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 by lazy { + slab.scope.internalSymbols.map { symbol -> + CompilableSymbol(this, symbol) + } + } + + fun compilableSymbolOf(symbol: Symbol): CompilableSymbol? = compilableSymbols.firstOrNull { + it.scopeSymbol.symbol == symbol + } +} diff --git a/compiler/src/main/kotlin/gay/pizza/pork/compiler/CompilableSymbol.kt b/compiler/src/main/kotlin/gay/pizza/pork/compiler/CompilableSymbol.kt new file mode 100644 index 0000000..fecbe67 --- /dev/null +++ b/compiler/src/main/kotlin/gay/pizza/pork/compiler/CompilableSymbol.kt @@ -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 by lazy { compile() } + + val usedSymbols: List + get() = scopeSymbol.scope.usedSymbols + + private fun compile(): List { + 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}" +} diff --git a/compiler/src/main/kotlin/gay/pizza/pork/compiler/CompiledWorldLayout.kt b/compiler/src/main/kotlin/gay/pizza/pork/compiler/CompiledWorldLayout.kt new file mode 100644 index 0000000..b3a7760 --- /dev/null +++ b/compiler/src/main/kotlin/gay/pizza/pork/compiler/CompiledWorldLayout.kt @@ -0,0 +1,38 @@ +package gay.pizza.pork.compiler + +import gay.pizza.pork.bytecode.* + +class CompiledWorldLayout(val compiler: Compiler) : StubResolutionContext { + private val allStubOps = mutableListOf() + private val symbolTable = mutableMapOf() + + 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 { + val ops = mutableListOf() + 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() + for (item in compiler.constantPool.all()) { + constantPool.add(item.value) + } + return CompiledWorld(ConstantPool(constantPool), SymbolTable(symbolTable.values.toList()), patch()) + } +} diff --git a/compiler/src/main/kotlin/gay/pizza/pork/compiler/Compiler.kt b/compiler/src/main/kotlin/gay/pizza/pork/compiler/Compiler.kt new file mode 100644 index 0000000..a9395d1 --- /dev/null +++ b/compiler/src/main/kotlin/gay/pizza/pork/compiler/Compiler.kt @@ -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 = 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, + 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() + contributeCompiledSymbols(usedSymbolSet, entryPointSymbol.scopeSymbol, entryPointSymbol) + val layout = CompiledWorldLayout(this) + for (used in usedSymbolSet) { + layout.add(used) + } + return layout.layoutCompiledWorld() + } +} diff --git a/compiler/src/main/kotlin/gay/pizza/pork/compiler/ComputableState.kt b/compiler/src/main/kotlin/gay/pizza/pork/compiler/ComputableState.kt new file mode 100644 index 0000000..b6aec68 --- /dev/null +++ b/compiler/src/main/kotlin/gay/pizza/pork/compiler/ComputableState.kt @@ -0,0 +1,7 @@ +package gay.pizza.pork.compiler + +class ComputableState(val computation: (X) -> T) { + private val state = StoredState() + + fun of(key: X): T = state.computeIfAbsent(key, computation) +} diff --git a/compiler/src/main/kotlin/gay/pizza/pork/compiler/LoopState.kt b/compiler/src/main/kotlin/gay/pizza/pork/compiler/LoopState.kt new file mode 100644 index 0000000..09f0747 --- /dev/null +++ b/compiler/src/main/kotlin/gay/pizza/pork/compiler/LoopState.kt @@ -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 +) diff --git a/compiler/src/main/kotlin/gay/pizza/pork/compiler/PatchRelOp.kt b/compiler/src/main/kotlin/gay/pizza/pork/compiler/PatchRelOp.kt new file mode 100644 index 0000000..3ef05f5 --- /dev/null +++ b/compiler/src/main/kotlin/gay/pizza/pork/compiler/PatchRelOp.kt @@ -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) { + arguments[index] = context.resolveJumpTarget(symbol) + rel.rel + } + + override fun toString(): String = "PatchRelOp(${op}, ${index}, ${symbol}, ${rel})" +} diff --git a/compiler/src/main/kotlin/gay/pizza/pork/compiler/PatchSymOp.kt b/compiler/src/main/kotlin/gay/pizza/pork/compiler/PatchSymOp.kt new file mode 100644 index 0000000..6c808dd --- /dev/null +++ b/compiler/src/main/kotlin/gay/pizza/pork/compiler/PatchSymOp.kt @@ -0,0 +1,13 @@ +package gay.pizza.pork.compiler + +import gay.pizza.pork.bytecode.Op + +class PatchSymOp(op: Op, val patches: Map) : StubOp(op) { + override fun patch(context: StubResolutionContext, arguments: MutableList) { + for ((index, symbol) in patches) { + arguments[index] = context.resolveJumpTarget(symbol) + } + } + + override fun toString(): String = "PatchSymOp(${op}, ${patches})" +} diff --git a/compiler/src/main/kotlin/gay/pizza/pork/compiler/StaticOp.kt b/compiler/src/main/kotlin/gay/pizza/pork/compiler/StaticOp.kt new file mode 100644 index 0000000..e1890b6 --- /dev/null +++ b/compiler/src/main/kotlin/gay/pizza/pork/compiler/StaticOp.kt @@ -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})" +} diff --git a/compiler/src/main/kotlin/gay/pizza/pork/compiler/StoredState.kt b/compiler/src/main/kotlin/gay/pizza/pork/compiler/StoredState.kt new file mode 100644 index 0000000..15b9307 --- /dev/null +++ b/compiler/src/main/kotlin/gay/pizza/pork/compiler/StoredState.kt @@ -0,0 +1,11 @@ +package gay.pizza.pork.compiler + +class StoredState { + private val state = mutableMapOf() + + 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 + } +} diff --git a/compiler/src/main/kotlin/gay/pizza/pork/compiler/StubOp.kt b/compiler/src/main/kotlin/gay/pizza/pork/compiler/StubOp.kt new file mode 100644 index 0000000..df3a5c9 --- /dev/null +++ b/compiler/src/main/kotlin/gay/pizza/pork/compiler/StubOp.kt @@ -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) {} +} diff --git a/compiler/src/main/kotlin/gay/pizza/pork/compiler/StubOpEmitter.kt b/compiler/src/main/kotlin/gay/pizza/pork/compiler/StubOpEmitter.kt new file mode 100644 index 0000000..e006f22 --- /dev/null +++ b/compiler/src/main/kotlin/gay/pizza/pork/compiler/StubOpEmitter.kt @@ -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() { + private val ops = mutableListOf() + + private var loopState: LoopState? = null + private val localVariables = mutableListOf>() + 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() + var variableArguments: List? = 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) { + ops.add(StaticOp(Op(code, arguments))) + } + + private fun patch(code: Opcode, arguments: List, index: Int, symbol: CompilableSymbol, rel: MutableRel) { + ops.add(PatchRelOp(Op(code, arguments), index, symbol, rel)) + } + + private fun patch(code: Opcode, arguments: List, index: Int, symbol: CompilableSymbol, rel: UInt) { + ops.add(PatchRelOp(Op(code, arguments), index, symbol, MutableRel(rel))) + } + + private fun patch(code: Opcode, arguments: List, patches: Map) { + ops.add(PatchSymOp(Op(code, arguments), patches)) + } + + fun ops(): List = 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 + ) +} diff --git a/compiler/src/main/kotlin/gay/pizza/pork/compiler/StubResolutionContext.kt b/compiler/src/main/kotlin/gay/pizza/pork/compiler/StubResolutionContext.kt new file mode 100644 index 0000000..cb8d1d1 --- /dev/null +++ b/compiler/src/main/kotlin/gay/pizza/pork/compiler/StubResolutionContext.kt @@ -0,0 +1,5 @@ +package gay.pizza.pork.compiler + +interface StubResolutionContext { + fun resolveJumpTarget(symbol: CompilableSymbol): UInt +} diff --git a/compiler/src/main/kotlin/gay/pizza/pork/compiler/StubVar.kt b/compiler/src/main/kotlin/gay/pizza/pork/compiler/StubVar.kt new file mode 100644 index 0000000..1209be7 --- /dev/null +++ b/compiler/src/main/kotlin/gay/pizza/pork/compiler/StubVar.kt @@ -0,0 +1,8 @@ +package gay.pizza.pork.compiler + +import gay.pizza.pork.ast.gen.Symbol + +class StubVar( + val index: UInt, + val symbol: Symbol +) diff --git a/evaluator/build.gradle.kts b/evaluator/build.gradle.kts index 9cf56b2..b455a39 100644 --- a/evaluator/build.gradle.kts +++ b/evaluator/build.gradle.kts @@ -4,6 +4,7 @@ plugins { dependencies { api(project(":ast")) + api(project(":execution")) api(project(":frontend")) implementation(project(":common")) diff --git a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/CompilationUnitContext.kt b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/CompilationUnitContext.kt deleted file mode 100644 index a4c5f3c..0000000 --- a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/CompilationUnitContext.kt +++ /dev/null @@ -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() - 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") - ) - ) - ) - } -} diff --git a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/EvaluationVisitor.kt b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/EvaluationVisitor.kt index 5b55760..2e55783 100644 --- a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/EvaluationVisitor.kt +++ b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/EvaluationVisitor.kt @@ -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 { +class EvaluationVisitor(root: Scope, val stack: CallStack) : FunctionLevelVisitor() { 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 { 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 { } } - 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 { 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 { 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") } diff --git a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/Evaluator.kt b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/Evaluator.kt index 4596b26..36d9230 100644 --- a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/Evaluator.kt +++ b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/Evaluator.kt @@ -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() +class Evaluator(val world: World) : ExecutionContextProvider { + private val scope = Scope.root() + private val contexts = mutableMapOf() private val nativeProviders = mutableMapOf() - 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) + } } diff --git a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/EvaluatorExecutionContext.kt b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/EvaluatorExecutionContext.kt new file mode 100644 index 0000000..22ba4e5 --- /dev/null +++ b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/EvaluatorExecutionContext.kt @@ -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()) + } +} diff --git a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/FunctionContext.kt b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/FunctionContext.kt index af24bbb..4474edd 100644 --- a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/FunctionContext.kt +++ b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/FunctionContext.kt @@ -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) diff --git a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/InternalNativeProvider.kt b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/InternalNativeProvider.kt index c0c04ef..e308acd 100644 --- a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/InternalNativeProvider.kt +++ b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/InternalNativeProvider.kt @@ -18,7 +18,7 @@ class InternalNativeProvider(val quiet: Boolean = false) : NativeProvider { override fun provideNativeFunction( definitions: List, arguments: List, - inside: CompilationUnitContext + inside: SlabContext ): CallableFunction { val definition = definitions[0] return functions[definition] ?: throw RuntimeException("Unknown Internal Function: $definition") diff --git a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/NativeProvider.kt b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/NativeProvider.kt index 4ca6380..24fa606 100644 --- a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/NativeProvider.kt +++ b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/NativeProvider.kt @@ -3,5 +3,5 @@ package gay.pizza.pork.evaluator import gay.pizza.pork.ast.gen.ArgumentSpec interface NativeProvider { - fun provideNativeFunction(definitions: List, arguments: List, inside: CompilationUnitContext): CallableFunction + fun provideNativeFunction(definitions: List, arguments: List, inside: SlabContext): CallableFunction } diff --git a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/SlabContext.kt b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/SlabContext.kt new file mode 100644 index 0000000..c1085c4 --- /dev/null +++ b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/SlabContext.kt @@ -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) + } + } +} diff --git a/examples/count.pork b/examples/count.pork index 7736387..4a3304e 100644 --- a/examples/count.pork +++ b/examples/count.pork @@ -1,9 +1,11 @@ -let count = 5 - export func main() { var x = 1 - while x <= count { - println(x) + while x <= 5 { + if x == 3 { + println("The value is 3") + } else { + println("The value is not 3") + } x++ } } diff --git a/execution/build.gradle.kts b/execution/build.gradle.kts new file mode 100644 index 0000000..2fe7404 --- /dev/null +++ b/execution/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + id("gay.pizza.pork.module") +} + +dependencies { + api(project(":frontend")) + implementation(project(":common")) +} diff --git a/execution/src/main/kotlin/gay/pizza/pork/execution/ExecutionContext.kt b/execution/src/main/kotlin/gay/pizza/pork/execution/ExecutionContext.kt new file mode 100644 index 0000000..57f4e59 --- /dev/null +++ b/execution/src/main/kotlin/gay/pizza/pork/execution/ExecutionContext.kt @@ -0,0 +1,5 @@ +package gay.pizza.pork.execution + +interface ExecutionContext { + fun execute() +} diff --git a/execution/src/main/kotlin/gay/pizza/pork/execution/ExecutionContextProvider.kt b/execution/src/main/kotlin/gay/pizza/pork/execution/ExecutionContextProvider.kt new file mode 100644 index 0000000..af3c085 --- /dev/null +++ b/execution/src/main/kotlin/gay/pizza/pork/execution/ExecutionContextProvider.kt @@ -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 +} diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiNativeProvider.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiNativeProvider.kt index aa37f98..346fba1 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiNativeProvider.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiNativeProvider.kt @@ -25,7 +25,7 @@ class FfiNativeProvider : NativeProvider { override fun provideNativeFunction( definitions: List, arguments: List, - inside: CompilationUnitContext + inside: SlabContext ): CallableFunction { if (definitions[0] == "internal") { val internal = internalFunctions[definitions[1]] ?: @@ -73,7 +73,7 @@ class FfiNativeProvider : NativeProvider { } } - private fun addStructDefs(ffiTypeRegistry: FfiTypeRegistry, types: List, inside: CompilationUnitContext) { + private fun addStructDefs(ffiTypeRegistry: FfiTypeRegistry, types: List, inside: SlabContext) { for (parameter in types) { if (!parameter.startsWith("struct ")) { continue diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaAutogenContentSource.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaAutogenContentSource.kt index 3596595..4bd9f34 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaAutogenContentSource.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaAutogenContentSource.kt @@ -15,5 +15,5 @@ object JavaAutogenContentSource : ContentSource { return StringCharSource(content) } - override fun stableContentIdentity(path: String): String = path + override fun stableContentPath(path: String): String = path } diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaNativeProvider.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaNativeProvider.kt index 4dfaf38..a299bef 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaNativeProvider.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaNativeProvider.kt @@ -2,7 +2,7 @@ package gay.pizza.pork.ffi import gay.pizza.pork.ast.gen.ArgumentSpec 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.None import java.lang.invoke.MethodHandles @@ -14,7 +14,7 @@ class JavaNativeProvider : NativeProvider { override fun provideNativeFunction( definitions: List, arguments: List, - inside: CompilationUnitContext + inside: SlabContext ): CallableFunction { val functionDefinition = JavaFunctionDefinition.parse(definitions) val javaClass = lookupClass(functionDefinition.type) diff --git a/frontend/src/main/kotlin/gay/pizza/pork/frontend/ContentSource.kt b/frontend/src/main/kotlin/gay/pizza/pork/frontend/ContentSource.kt index 0d134fb..ecd86a0 100644 --- a/frontend/src/main/kotlin/gay/pizza/pork/frontend/ContentSource.kt +++ b/frontend/src/main/kotlin/gay/pizza/pork/frontend/ContentSource.kt @@ -4,5 +4,5 @@ import gay.pizza.pork.tokenizer.CharSource interface ContentSource { fun loadAsCharSource(path: String): CharSource - fun stableContentIdentity(path: String): String + fun stableContentPath(path: String): String } diff --git a/frontend/src/main/kotlin/gay/pizza/pork/frontend/DynamicImportSource.kt b/frontend/src/main/kotlin/gay/pizza/pork/frontend/DynamicImportSource.kt index 1a3d47c..c8036a8 100644 --- a/frontend/src/main/kotlin/gay/pizza/pork/frontend/DynamicImportSource.kt +++ b/frontend/src/main/kotlin/gay/pizza/pork/frontend/DynamicImportSource.kt @@ -1,7 +1,7 @@ package gay.pizza.pork.frontend class DynamicImportSource : ImportSource { - private val providers = mutableMapOf() + private val providers = mutableMapOf() override fun provideContentSource(form: String): ContentSource { return providers[form] ?: diff --git a/frontend/src/main/kotlin/gay/pizza/pork/frontend/FsContentSource.kt b/frontend/src/main/kotlin/gay/pizza/pork/frontend/FsContentSource.kt index 5257f60..e942861 100644 --- a/frontend/src/main/kotlin/gay/pizza/pork/frontend/FsContentSource.kt +++ b/frontend/src/main/kotlin/gay/pizza/pork/frontend/FsContentSource.kt @@ -10,7 +10,7 @@ class FsContentSource(val root: FsPath) : ContentSource { override fun loadAsCharSource(path: String): CharSource = StringCharSource(asFsPath(path).readString()) - override fun stableContentIdentity(path: String): String = + override fun stableContentPath(path: String): String = asFsPath(path).fullPathString private fun asFsPath(path: String): FsPath { diff --git a/frontend/src/main/kotlin/gay/pizza/pork/frontend/Slab.kt b/frontend/src/main/kotlin/gay/pizza/pork/frontend/Slab.kt new file mode 100644 index 0000000..9852f5e --- /dev/null +++ b/frontend/src/main/kotlin/gay/pizza/pork/frontend/Slab.kt @@ -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 by lazy { + world.resolveAllImports(this) + } + + val scope: SlabScope by lazy { world.scope.index(this) } +} diff --git a/frontend/src/main/kotlin/gay/pizza/pork/frontend/SourceLocation.kt b/frontend/src/main/kotlin/gay/pizza/pork/frontend/SourceLocation.kt new file mode 100644 index 0000000..3969f52 --- /dev/null +++ b/frontend/src/main/kotlin/gay/pizza/pork/frontend/SourceLocation.kt @@ -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) +} diff --git a/frontend/src/main/kotlin/gay/pizza/pork/frontend/StableSourceKey.kt b/frontend/src/main/kotlin/gay/pizza/pork/frontend/StableSourceKey.kt new file mode 100644 index 0000000..71517db --- /dev/null +++ b/frontend/src/main/kotlin/gay/pizza/pork/frontend/StableSourceKey.kt @@ -0,0 +1,5 @@ +package gay.pizza.pork.frontend + +data class StableSourceKey(val form: String, val path: String) { + fun asSourceLocation(): SourceLocation = SourceLocation(form, path) +} diff --git a/frontend/src/main/kotlin/gay/pizza/pork/frontend/World.kt b/frontend/src/main/kotlin/gay/pizza/pork/frontend/World.kt index 7bfaf56..981a073 100644 --- a/frontend/src/main/kotlin/gay/pizza/pork/frontend/World.kt +++ b/frontend/src/main/kotlin/gay/pizza/pork/frontend/World.kt @@ -1,22 +1,25 @@ package gay.pizza.pork.frontend -import gay.pizza.pork.ast.gen.CompilationUnit 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.Parser import gay.pizza.pork.tokenizer.Tokenizer class World(val importSource: ImportSource) { - private val internalUnits = mutableMapOf() - private val importedUnits = mutableMapOf>() + private val preludeImportLocator = ImportLocator("std", "lang/prelude.pork") - val units: List - get() = internalUnits.values.toList() + private val internalSlabs = mutableMapOf() - private fun loadOneUnit(importLocator: ImportLocator): CompilationUnit { + val slabs: List + get() = internalSlabs.values.toList() + + val scope: WorldScope by lazy { WorldScope(this) } + + private fun loadOneSlab(importLocator: ImportLocator): Slab { val contentSource = pickContentSource(importLocator.form) - val stableKey = stableIdentity(importLocator, contentSource = contentSource) - val cached = internalUnits[stableKey] + val stableKey = stableSourceKey(importLocator, contentSource = contentSource) + val cached = internalSlabs[stableKey] if (cached != null) { return cached } @@ -24,40 +27,39 @@ class World(val importSource: ImportSource) { val tokenizer = Tokenizer(charSource) val parser = Parser(tokenizer, DiscardNodeAttribution) val unit = parser.parseCompilationUnit() - internalUnits[stableKey] = unit - return unit + val slab = Slab(world = this, location = stableKey.asSourceLocation(), compilationUnit = unit) + internalSlabs[stableKey] = slab + return slab } - private fun resolveAllImports(unit: CompilationUnit): Set { - val units = mutableSetOf() - for (declaration in unit.declarations.filterIsInstance()) { + internal fun resolveAllImports(slab: Slab): List { + val slabs = mutableListOf() + if (slab.location.form != preludeImportLocator.form && + slab.location.filePath != preludeImportLocator.path) { + slabs.add(loadOneSlab(preludeImportLocator)) + } + for (declaration in slab.compilationUnit.declarations.filterIsInstance()) { val importPath = declaration.path.components.joinToString("/") { it.id } + ".pork" val importLocator = ImportLocator(declaration.form.id, importPath) - val importedUnit = loadOneUnit(importLocator) - units.add(importedUnit) + val importedModule = loadOneSlab(importLocator) + slabs.add(importedModule) } - importedUnits[unit] = units - return units + return slabs } - fun load(importLocator: ImportLocator): CompilationUnit { - val unit = loadOneUnit(importLocator) - resolveAllImports(unit) - return unit + fun load(importLocator: ImportLocator): Slab { + return loadOneSlab(importLocator) } - fun importedBy(unit: CompilationUnit): Set = - importedUnits[unit] ?: emptySet() - private fun pickContentSource(form: String): ContentSource = importSource.provideContentSource(form) - fun stableIdentity( + fun stableSourceKey( importLocator: ImportLocator, contentSource: ContentSource = pickContentSource(importLocator.form) - ): String { + ): StableSourceKey { val formKey = importLocator.form - val stableIdentity = contentSource.stableContentIdentity(importLocator.path) - return "[${formKey}][${stableIdentity}]" + val stableContentPath = contentSource.stableContentPath(importLocator.path) + return StableSourceKey(formKey, stableContentPath) } } diff --git a/frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/CompilationUnitScope.kt b/frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/CompilationUnitScope.kt deleted file mode 100644 index 4aa5f04..0000000 --- a/frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/CompilationUnitScope.kt +++ /dev/null @@ -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() - val internalSymbols = mutableSetOf() - - 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 { - val allSymbols = mutableMapOf() - 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() - } -} diff --git a/frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/DefinitionScope.kt b/frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/DefinitionScope.kt new file mode 100644 index 0000000..6fcaa76 --- /dev/null +++ b/frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/DefinitionScope.kt @@ -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 by lazy { + val symbols = mutableListOf() + 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) +} diff --git a/frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/ExternalSymbolUsageAnalyzer.kt b/frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/ExternalSymbolUsageAnalyzer.kt new file mode 100644 index 0000000..7f4ae28 --- /dev/null +++ b/frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/ExternalSymbolUsageAnalyzer.kt @@ -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() { + private val symbols = mutableSetOf() + private val internalSymbols = mutableListOf>() + + val usedSymbols: Set + 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) + } + } +} diff --git a/frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/ScopeSymbol.kt b/frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/ScopeSymbol.kt index 98a6999..c386069 100644 --- a/frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/ScopeSymbol.kt +++ b/frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/ScopeSymbol.kt @@ -1,11 +1,9 @@ package gay.pizza.pork.frontend.scope import gay.pizza.pork.ast.gen.Definition -import gay.pizza.pork.ast.gen.Node +import gay.pizza.pork.ast.gen.Symbol -class ScopeSymbol( - val compilationUnit: Node, - val definition: Definition -) { - val symbol = definition.symbol +class ScopeSymbol(val slabScope: SlabScope, val definition: Definition) { + val symbol: Symbol = definition.symbol + val scope: DefinitionScope by lazy { DefinitionScope(slabScope, definition) } } diff --git a/frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/SlabScope.kt b/frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/SlabScope.kt new file mode 100644 index 0000000..7f68473 --- /dev/null +++ b/frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/SlabScope.kt @@ -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() + private val internalSymbolsList = mutableSetOf() + + val externalSymbols: Set + get() = externalSymbolsList + + val internalSymbols: Set + get() = internalSymbolsList + + val internallyVisibleSymbols: List 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 { + val allSymbols = mutableMapOf() + 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 +} diff --git a/frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/VisibleScopeSymbol.kt b/frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/VisibleScopeSymbol.kt index a41976b..2798362 100644 --- a/frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/VisibleScopeSymbol.kt +++ b/frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/VisibleScopeSymbol.kt @@ -1,8 +1,8 @@ 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 - get() = visibleToUnit == scopeSymbol.compilationUnit + get() = visibleToSlab == scopeSymbol.slabScope.slab } diff --git a/frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/WorldScope.kt b/frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/WorldScope.kt index a42dbec..8b44c34 100644 --- a/frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/WorldScope.kt +++ b/frontend/src/main/kotlin/gay/pizza/pork/frontend/scope/WorldScope.kt @@ -1,24 +1,24 @@ package gay.pizza.pork.frontend.scope -import gay.pizza.pork.ast.gen.CompilationUnit import gay.pizza.pork.frontend.World +import gay.pizza.pork.frontend.Slab class WorldScope(val world: World) { - private val compilationUnitScopes = mutableMapOf() + private val slabScopes = mutableMapOf() fun indexAll() { - for (unit in world.units) { - index(unit) + for (module in world.slabs) { + index(module) } } - fun index(unit: CompilationUnit): CompilationUnitScope = - scope(unit).apply { + fun index(slab: Slab): SlabScope = + scope(slab).apply { index() } - fun scope(unit: CompilationUnit): CompilationUnitScope = - compilationUnitScopes.computeIfAbsent(unit) { - CompilationUnitScope(this, unit) + fun scope(slab: Slab): SlabScope = + slabScopes.computeIfAbsent(slab) { + SlabScope(this, slab) } } diff --git a/minimal/src/main/kotlin/gay/pizza/pork/minimal/Tool.kt b/minimal/src/main/kotlin/gay/pizza/pork/minimal/Tool.kt index c7d818d..ee4a8f5 100644 --- a/minimal/src/main/kotlin/gay/pizza/pork/minimal/Tool.kt +++ b/minimal/src/main/kotlin/gay/pizza/pork/minimal/Tool.kt @@ -36,16 +36,16 @@ abstract class Tool { fun visit(visitor: NodeVisitor): T = visitor.visit(parse()) - fun loadMainFunction(scope: Scope, setupEvaluator: Evaluator.() -> Unit = {}): CallableFunction { + fun loadMainFunction(setupEvaluator: Evaluator.() -> Unit = {}): CallableFunction { val world = buildWorld() - val evaluator = Evaluator(world, scope) + val evaluator = Evaluator(world) setupEvaluator(evaluator) val resultingScope = evaluator.evaluate(rootImportLocator) return resultingScope.value("main") as CallableFunction } - fun loadMainFunctionStandard(scope: Scope, quiet: Boolean = false): CallableFunction = - loadMainFunction(scope, setupEvaluator = { + fun loadMainFunctionStandard(quiet: Boolean = false): CallableFunction = + loadMainFunction(setupEvaluator = { addNativeProvider("internal", InternalNativeProvider(quiet = quiet)) addNativeProvider("ffi", FfiNativeProvider()) addNativeProvider("java", JavaNativeProvider()) @@ -60,8 +60,8 @@ abstract class Tool { return World(dynamicImportSource) } - fun run(scope: Scope, quiet: Boolean = false) { - val main = loadMainFunctionStandard(scope, quiet = quiet) + fun run(quiet: Boolean = false) { + val main = loadMainFunctionStandard(quiet = quiet) main.call(emptyList(), CallStack()) } } diff --git a/minimal/src/main/kotlin/gay/pizza/pork/minimal/main.kt b/minimal/src/main/kotlin/gay/pizza/pork/minimal/main.kt index cf4a195..e185ec4 100644 --- a/minimal/src/main/kotlin/gay/pizza/pork/minimal/main.kt +++ b/minimal/src/main/kotlin/gay/pizza/pork/minimal/main.kt @@ -11,5 +11,5 @@ fun main(args: Array) { } val path = PlatformFsProvider.resolve(args[0]) val tool = FileTool(path) - tool.run(Scope.root()) + tool.run() } diff --git a/settings.gradle.kts b/settings.gradle.kts index 4191bba..d9ccc87 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -6,8 +6,12 @@ include( ":common", ":tokenizer", ":ast", + ":bytecode", ":parser", ":frontend", + ":compiler", + ":execution", + ":vm", ":evaluator", ":stdlib", ":ffi", diff --git a/stdlib/src/main/kotlin/gay/pizza/pork/stdlib/PorkStdlib.kt b/stdlib/src/main/kotlin/gay/pizza/pork/stdlib/PorkStdlib.kt index 6ae7c61..6761b44 100644 --- a/stdlib/src/main/kotlin/gay/pizza/pork/stdlib/PorkStdlib.kt +++ b/stdlib/src/main/kotlin/gay/pizza/pork/stdlib/PorkStdlib.kt @@ -34,7 +34,7 @@ object PorkStdlib : ContentSource { return StringCharSource(readPorkFile(path)) } - override fun stableContentIdentity(path: String): String { + override fun stableContentPath(path: String): String { return path } } diff --git a/stdlib/src/main/pork/ffi/struct.pork b/stdlib/src/main/pork/ffi/struct.pork index 6de85db..bd3b999 100644 --- a/stdlib/src/main/pork/ffi/struct.pork +++ b/stdlib/src/main/pork/ffi/struct.pork @@ -1,7 +1,7 @@ export func ffiStructDefine(items...) native ffi "internal" "ffiStructDefine" -export func ffiStructAllocate(struct) +export func ffiStructAllocate(def) native ffi "internal" "ffiStructAllocate" export func ffiStructValue(def, field, value) diff --git a/tokenizer/src/main/kotlin/gay/pizza/pork/tokenizer/TokenType.kt b/tokenizer/src/main/kotlin/gay/pizza/pork/tokenizer/TokenType.kt index f132fa3..32bd769 100644 --- a/tokenizer/src/main/kotlin/gay/pizza/pork/tokenizer/TokenType.kt +++ b/tokenizer/src/main/kotlin/gay/pizza/pork/tokenizer/TokenType.kt @@ -76,7 +76,6 @@ enum class TokenType(vararg val properties: TokenTypeProperty) { ))), BlockComment(CharConsume(MatchedCharConsumer("/*", "*/")), CommentFamily), LineComment(CharConsume(MatchedCharConsumer("//", "\n", AllowEofTermination)), CommentFamily), - Struct(ManyChars("struct"), KeywordFamily), EndOfFile; val promotions: List = diff --git a/tool/build.gradle.kts b/tool/build.gradle.kts index a8de78f..9174b7c 100644 --- a/tool/build.gradle.kts +++ b/tool/build.gradle.kts @@ -7,6 +7,8 @@ plugins { dependencies { api(project(":minimal")) + api(project(":compiler")) + api(project(":vm")) api("com.github.ajalt.clikt:clikt:4.2.0") implementation(project(":common")) diff --git a/tool/src/main/kotlin/gay/pizza/pork/tool/CompileCommand.kt b/tool/src/main/kotlin/gay/pizza/pork/tool/CompileCommand.kt new file mode 100644 index 0000000..bf53e06 --- /dev/null +++ b/tool/src/main/kotlin/gay/pizza/pork/tool/CompileCommand.kt @@ -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() + } +} diff --git a/tool/src/main/kotlin/gay/pizza/pork/tool/RootCommand.kt b/tool/src/main/kotlin/gay/pizza/pork/tool/RootCommand.kt index 393e353..cf723aa 100644 --- a/tool/src/main/kotlin/gay/pizza/pork/tool/RootCommand.kt +++ b/tool/src/main/kotlin/gay/pizza/pork/tool/RootCommand.kt @@ -17,7 +17,8 @@ class RootCommand : CliktCommand( AstCommand(), AttributeCommand(), ScopeAnalysisCommand(), - CopyStdlibCommand() + CopyStdlibCommand(), + CompileCommand() ) } diff --git a/tool/src/main/kotlin/gay/pizza/pork/tool/RunCommand.kt b/tool/src/main/kotlin/gay/pizza/pork/tool/RunCommand.kt index 70777e9..9f81f34 100644 --- a/tool/src/main/kotlin/gay/pizza/pork/tool/RunCommand.kt +++ b/tool/src/main/kotlin/gay/pizza/pork/tool/RunCommand.kt @@ -6,8 +6,6 @@ import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.option import gay.pizza.dough.fs.PlatformFsProvider import gay.pizza.pork.evaluator.* -import gay.pizza.pork.ffi.FfiNativeProvider -import gay.pizza.pork.ffi.JavaNativeProvider import gay.pizza.pork.minimal.FileTool class RunCommand : CliktCommand(help = "Run Program", name = "run") { @@ -19,12 +17,11 @@ class RunCommand : CliktCommand(help = "Run Program", name = "run") { override fun run() { val tool = FileTool(PlatformFsProvider.resolve(path)) - val scope = Scope.root() - val main = tool.loadMainFunctionStandard(scope, quiet = quiet) + val main = tool.loadMainFunctionStandard(quiet = quiet) if (dumpScope) { val functionContext = main as FunctionContext - val internalScope = functionContext.compilationUnitContext.internalScope + val internalScope = functionContext.slabContext.internalScope internalScope.crawlScopePath { key, path -> println("[scope] $key [${path.joinToString(" -> ")}]") } diff --git a/tool/src/main/kotlin/gay/pizza/pork/tool/ScopeAnalysisCommand.kt b/tool/src/main/kotlin/gay/pizza/pork/tool/ScopeAnalysisCommand.kt index dc73135..d85d2a6 100644 --- a/tool/src/main/kotlin/gay/pizza/pork/tool/ScopeAnalysisCommand.kt +++ b/tool/src/main/kotlin/gay/pizza/pork/tool/ScopeAnalysisCommand.kt @@ -15,12 +15,12 @@ class ScopeAnalysisCommand : CliktCommand(help = "Run Scope Analysis", name = "s val root = world.load(tool.rootImportLocator) val scope = WorldScope(world).apply { index(root) } val rootScope = scope.scope(root) - val visibleScopeSymbols = rootScope.findInternallyVisibleSymbols() - for (visibleScopeSymbol in visibleScopeSymbols) { + for (visibleScopeSymbol in rootScope.internallyVisibleSymbols) { println( "symbol ${visibleScopeSymbol.scopeSymbol.symbol.id} " + "type=${visibleScopeSymbol.scopeSymbol.definition.type.name} " + - "internal=${visibleScopeSymbol.isInternalSymbol}" + "internal=${visibleScopeSymbol.isInternalSymbol} " + + "slab=${visibleScopeSymbol.scopeSymbol.slab.location.commonFriendlyName}" ) } } diff --git a/vm/build.gradle.kts b/vm/build.gradle.kts new file mode 100644 index 0000000..62e535a --- /dev/null +++ b/vm/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + id("gay.pizza.pork.module") +} + +dependencies { + api(project(":execution")) + api(project(":bytecode")) + api(project(":compiler")) + + implementation(project(":common")) +} diff --git a/vm/src/main/kotlin/gay/pizza/pork/vm/InternalMachine.kt b/vm/src/main/kotlin/gay/pizza/pork/vm/InternalMachine.kt new file mode 100644 index 0000000..4d7817b --- /dev/null +++ b/vm/src/main/kotlin/gay/pizza/pork/vm/InternalMachine.kt @@ -0,0 +1,80 @@ +package gay.pizza.pork.vm + +import gay.pizza.pork.bytecode.CompiledWorld + +class InternalMachine(val world: CompiledWorld, val handlers: List) { + 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(EndOfCode) + private val locals = mutableListOf>( + 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 +} diff --git a/vm/src/main/kotlin/gay/pizza/pork/vm/OpHandler.kt b/vm/src/main/kotlin/gay/pizza/pork/vm/OpHandler.kt new file mode 100644 index 0000000..e2a20f7 --- /dev/null +++ b/vm/src/main/kotlin/gay/pizza/pork/vm/OpHandler.kt @@ -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) +} diff --git a/vm/src/main/kotlin/gay/pizza/pork/vm/VirtualMachine.kt b/vm/src/main/kotlin/gay/pizza/pork/vm/VirtualMachine.kt new file mode 100644 index 0000000..966a07e --- /dev/null +++ b/vm/src/main/kotlin/gay/pizza/pork/vm/VirtualMachine.kt @@ -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() + } +} diff --git a/vm/src/main/kotlin/gay/pizza/pork/vm/VirtualMachineException.kt b/vm/src/main/kotlin/gay/pizza/pork/vm/VirtualMachineException.kt new file mode 100644 index 0000000..ad8ec3b --- /dev/null +++ b/vm/src/main/kotlin/gay/pizza/pork/vm/VirtualMachineException.kt @@ -0,0 +1,3 @@ +package gay.pizza.pork.vm + +class VirtualMachineException(message: String) : RuntimeException(message) diff --git a/vm/src/main/kotlin/gay/pizza/pork/vm/VirtualMachineProvider.kt b/vm/src/main/kotlin/gay/pizza/pork/vm/VirtualMachineProvider.kt new file mode 100644 index 0000000..83896df --- /dev/null +++ b/vm/src/main/kotlin/gay/pizza/pork/vm/VirtualMachineProvider.kt @@ -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) + } +} diff --git a/vm/src/main/kotlin/gay/pizza/pork/vm/ops/AddOpHandler.kt b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/AddOpHandler.kt new file mode 100644 index 0000000..9029273 --- /dev/null +++ b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/AddOpHandler.kt @@ -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) + } +} diff --git a/vm/src/main/kotlin/gay/pizza/pork/vm/ops/CallOpHandler.kt b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/CallOpHandler.kt new file mode 100644 index 0000000..b7c699c --- /dev/null +++ b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/CallOpHandler.kt @@ -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() + } +} diff --git a/vm/src/main/kotlin/gay/pizza/pork/vm/ops/CompareEqualOpHandler.kt b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/CompareEqualOpHandler.kt new file mode 100644 index 0000000..a221dce --- /dev/null +++ b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/CompareEqualOpHandler.kt @@ -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()) + } +} diff --git a/vm/src/main/kotlin/gay/pizza/pork/vm/ops/CompareLesserEqualOpHandler.kt b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/CompareLesserEqualOpHandler.kt new file mode 100644 index 0000000..b60f326 --- /dev/null +++ b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/CompareLesserEqualOpHandler.kt @@ -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) + } +} diff --git a/vm/src/main/kotlin/gay/pizza/pork/vm/ops/ConstantOpHandler.kt b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/ConstantOpHandler.kt new file mode 100644 index 0000000..7dc4a1c --- /dev/null +++ b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/ConstantOpHandler.kt @@ -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]) + } +} diff --git a/vm/src/main/kotlin/gay/pizza/pork/vm/ops/FalseOpHandler.kt b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/FalseOpHandler.kt new file mode 100644 index 0000000..106c4c7 --- /dev/null +++ b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/FalseOpHandler.kt @@ -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) + } +} diff --git a/vm/src/main/kotlin/gay/pizza/pork/vm/ops/IntegerOpHandler.kt b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/IntegerOpHandler.kt new file mode 100644 index 0000000..691f286 --- /dev/null +++ b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/IntegerOpHandler.kt @@ -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()) + } +} diff --git a/vm/src/main/kotlin/gay/pizza/pork/vm/ops/JumpIfOpHandler.kt b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/JumpIfOpHandler.kt new file mode 100644 index 0000000..197b4b6 --- /dev/null +++ b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/JumpIfOpHandler.kt @@ -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]) + } + } +} diff --git a/vm/src/main/kotlin/gay/pizza/pork/vm/ops/JumpOpHandler.kt b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/JumpOpHandler.kt new file mode 100644 index 0000000..93ec608 --- /dev/null +++ b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/JumpOpHandler.kt @@ -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]) + } +} diff --git a/vm/src/main/kotlin/gay/pizza/pork/vm/ops/ListOpHandler.kt b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/ListOpHandler.kt new file mode 100644 index 0000000..98ea28e --- /dev/null +++ b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/ListOpHandler.kt @@ -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() + for (i in 1u..count) { + val item = machine.pop() + list.add(item) + } + machine.push(list.reversed()) + } +} diff --git a/vm/src/main/kotlin/gay/pizza/pork/vm/ops/LoadLocalOpHandler.kt b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/LoadLocalOpHandler.kt new file mode 100644 index 0000000..3dc4118 --- /dev/null +++ b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/LoadLocalOpHandler.kt @@ -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]) + } +} diff --git a/vm/src/main/kotlin/gay/pizza/pork/vm/ops/NativeOpHandler.kt b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/NativeOpHandler.kt new file mode 100644 index 0000000..18ae662 --- /dev/null +++ b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/NativeOpHandler.kt @@ -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() + for (i in 0 until countOfNativeDefs) { + defs.add(String(machine.pop() as ByteArray)) + } + } +} diff --git a/vm/src/main/kotlin/gay/pizza/pork/vm/ops/RetOpHandler.kt b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/RetOpHandler.kt new file mode 100644 index 0000000..62becc6 --- /dev/null +++ b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/RetOpHandler.kt @@ -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()) + } +} diff --git a/vm/src/main/kotlin/gay/pizza/pork/vm/ops/ScopeInOpHandler.kt b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/ScopeInOpHandler.kt new file mode 100644 index 0000000..b4aea85 --- /dev/null +++ b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/ScopeInOpHandler.kt @@ -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) {} +} diff --git a/vm/src/main/kotlin/gay/pizza/pork/vm/ops/ScopeOutOpHandler.kt b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/ScopeOutOpHandler.kt new file mode 100644 index 0000000..02c3785 --- /dev/null +++ b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/ScopeOutOpHandler.kt @@ -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) {} +} diff --git a/vm/src/main/kotlin/gay/pizza/pork/vm/ops/StoreLocalOpHandler.kt b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/StoreLocalOpHandler.kt new file mode 100644 index 0000000..f53e395 --- /dev/null +++ b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/StoreLocalOpHandler.kt @@ -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]) + } +} diff --git a/vm/src/main/kotlin/gay/pizza/pork/vm/ops/TrueOpHandler.kt b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/TrueOpHandler.kt new file mode 100644 index 0000000..61036a8 --- /dev/null +++ b/vm/src/main/kotlin/gay/pizza/pork/vm/ops/TrueOpHandler.kt @@ -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) + } +}