Skip to content

Commit

Permalink
Merge pull request #202 from modelix/lr1-parser-subconcepts
Browse files Browse the repository at this point in the history
LR1 parser
  • Loading branch information
slisson authored Oct 22, 2024
2 parents 1e51403 + 9616cc6 commit 308beda
Show file tree
Hide file tree
Showing 81 changed files with 7,327 additions and 1,470 deletions.
10 changes: 5 additions & 5 deletions buildSrc/src/main/kotlin/org/modelix/CopyMps.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ val Project.mpsVersion: String get() {
"2021.1" to "2021.1.4",
"2021.2" to "2021.2.6",
"2021.3" to "2021.3.5",
"2022.2" to "2022.2.3",
"2022.3" to "2022.3.1",
"2023.2" to "2023.2",
"2023.3" to "2023.3",
"2024.1" to "2024.1-RC1",
"2022.2" to "2022.2.4",
"2022.3" to "2022.3.3",
"2023.2" to "2023.2.2",
"2023.3" to "2023.3.2",
"2024.1" to "2024.1.1",
)[it],
) { "Unknown MPS version: $it" }
}
Expand Down
35 changes: 18 additions & 17 deletions commitlint.config.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
module.exports = {
extends: ["@commitlint/config-conventional"],
rules: {
"scope-enum": [
2,
"always",
[
"deps",
"projectional-editor",
"mps-plugin",
],
extends: ["@commitlint/config-conventional"],
rules: {
"scope-enum": [
2,
"always",
[
"deps",
"projectional-editor",
"mps-plugin",
"interpreter"
],
],
"subject-case": [0, 'never'],
"body-max-line-length": [0, 'always'],
"footer-max-line-length": [0, 'always']
},
ignores: [
(message) => message.includes('skip-lint')
],
"subject-case": [0, 'never'],
"body-max-line-length": [0, 'always'],
"footer-max-line-length": [0, 'always']
},
ignores: [
(message) => message.includes('skip-lint')
],
};
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ kotlin-html = "org.jetbrains.kotlinx:kotlinx-html:0.11.0"
modelix-build-tools-lib = { group = "org.modelix.mps", name = "build-tools-lib", version.ref = "modelixBuildtools" }
kotlin-logging = { group = "io.github.oshai", name = "kotlin-logging", version = "7.0.0" }
slf4j-api = { group = "org.slf4j", name = "slf4j-api", version = "2.0.16" }
kotlin-collections-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version = "0.3.7" }
49 changes: 49 additions & 0 deletions interpreter-vm/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
plugins {
kotlin("multiplatform")
`maven-publish`
}

kotlin {
jvmToolchain(17)
jvm()
js(IR) {
browser {}
nodejs {
testTask {
useMocha {
timeout = "10s"
}
}
}
}

sourceSets {
val commonMain by getting {
dependencies {
implementation(libs.kotlin.collections.immutable)
implementation(coreLibs.modelix.incremental)
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}
val jvmMain by getting {
dependencies {
}
}
val jvmTest by getting {
dependencies {
}
}
val jsMain by getting {
dependencies {
}
}
val jsTest by getting {
dependencies {
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package org.modelix.interpreter.vm.core

class CallInstruction(val entryPoint: Instruction, val parameterCount: Int) : Instruction() {

override fun execute(state: VMState): VMState {
var newFrame = StackFrame(returnTo = next)
var newState = state
for (i in 0 until parameterCount) {
newState.popOperand().let {
newState = it.second
newFrame = newFrame.writeLocalMemory(ParameterKey<Any?>(i), it.first)
}
}
newState = newState.copy(callStack = newState.callStack.pushFrame(newFrame), nextInstruction = entryPoint)
return newState
}
}

class ReturnInstruction() : Instruction() {
override fun execute(state: VMState): VMState {
val (newCallStack, currentFrame) = state.callStack.popFrame()
check(currentFrame.operandStack.size == 1) { "Operand stack should contain a single value, but was: " + currentFrame.operandStack }
return state
.copy(nextInstruction = currentFrame.returnTo, callStack = newCallStack)
.pushOperand(currentFrame.operandStack.single())
}
}

class PushConstantInstruction<E>(val value: E) : Instruction() {
override fun execute(state: VMState): VMState {
return state.pushOperand(value)
}
}

class StoreInstruction<E>(val target: MemoryKey<in E>) : Instruction() {
override fun execute(state: VMState): VMState {
return state.popOperand().let { (value, newState) -> newState.writeMemory(target, value as E) }
}
}

class LoadInstruction<E>(val source: MemoryKey<out E>) : Instruction() {
override fun execute(state: VMState): VMState {
return state.pushOperand(state.readMemory(source))
}
}

abstract class BinaryOperationInstruction<Arg1, Arg2, Result>() : Instruction() {

abstract fun apply(arg1: Arg1, arg2: Arg2): Result

override fun execute(state: VMState): VMState {
var newState: VMState = state
var arg1: Arg1
var arg2: Arg2
newState.popOperand().let {
arg2 = it.first as Arg2
newState = it.second
}
newState.popOperand().let {
arg1 = it.first as Arg1
newState = it.second
}
newState = newState.pushOperand(apply(arg1, arg2))
return newState
}
}

class AddIntegersInstruction() : BinaryOperationInstruction<Int, Int, Int>() {
override fun apply(arg1: Int, arg2: Int): Int {
return arg1 + arg2
}
}

class MultiplyIntegersInstruction() : BinaryOperationInstruction<Int, Int, Int>() {
override fun apply(arg1: Int, arg2: Int): Int {
return arg1 * arg2
}
}

class JumpInstruction(val target: Instruction) : Instruction() {
override fun execute(state: VMState): VMState {
return state.copy(nextInstruction = target)
}
}

class ConditionalJumpInstruction(val condition: MemoryKey<out Boolean>, val target: Instruction) : Instruction() {
override fun execute(state: VMState): VMState {
return if (state.readMemory(condition)) state.copy(nextInstruction = target) else state
}
}

class MoveInstruction<E>(val source: MemoryKey<out E>, val target: MemoryKey<in E>) : Instruction() {
override fun execute(state: VMState): VMState {
return state.writeMemory(target, state.readMemory(source))
}
}

class NoOpInstruction : Instruction() {
override fun execute(state: VMState): VMState {
return state
}
}

data class NamedGlobalVarKey<E>(val name: String) : MemoryKey<E>(MemoryType.GLOBAL, name)
data class NamedLocalVarKey<E>(val name: String) : MemoryKey<E>(MemoryType.LOCAL, name)
data class ParameterKey<E>(val index: Int) : MemoryKey<E>(MemoryType.LOCAL, "parameter" + index)
data class ReturnValueKey<E>(val index: Int) : MemoryKey<E>(MemoryType.LOCAL, "returnValue" + index)
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package org.modelix.interpreter.vm.core

import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.PersistentMap
import kotlinx.collections.immutable.persistentHashMapOf
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.plus
import org.modelix.incremental.AtomicLong

class InterpreterVM(entryPoint: Instruction) {
private var state: VMState = VMState(nextInstruction = entryPoint)

fun isTerminated() = state.nextInstruction == null
fun run(): VMState {
while (!isTerminated()) {
singleStep()
}
return state
}

fun singleStep(): VMState {
val instruction = checkNotNull(state.nextInstruction) { "No more instructions to execute" }
return instruction.execute(state.copy(nextInstruction = instruction.next)).also { state = it }
}

fun <T> writeMemory(key: MemoryKey<in T>, value: T) {
state = state.writeMemory(key, value)
}
}

data class VMState(
val nextInstruction: Instruction?,
val globalMemory: Memory = Memory(),
val callStack: CallStack = CallStack().pushFrame(StackFrame(returnTo = null)),
) {
fun <T> readMemory(key: MemoryKey<out T>): T = key.memoryType.getMemory(this).read(key)
fun <T> writeMemory(key: MemoryKey<in T>, value: T): VMState {
return key.memoryType.setMemory(this, key.memoryType.getMemory(this).write(key, value))
}
fun updateCurrentFrame(body: (StackFrame) -> StackFrame): VMState {
return replaceCurrentFrame(body(callStack.currentFrame()))
}
fun replaceCurrentFrame(newFrame: StackFrame): VMState {
return copy(callStack = callStack.updateCurrentFrame(newFrame))
}
fun pushOperand(value: Any?): VMState = updateCurrentFrame { it.pushOperand(value) }
fun popOperand(): Pair<Any?, VMState> {
return callStack.currentFrame().popOperand().let { (value, newFrame) -> value to replaceCurrentFrame(newFrame) }
}
}

data class Memory(private val data: PersistentMap<MemoryKey<*>, Any?> = persistentHashMapOf()) {
fun hasKey(key: MemoryKey<*>): Boolean = data.containsKey(key)
fun <T> read(key: MemoryKey<out T>): T {
check(hasKey(key)) { "Uninitialized read: $key" }
return data[key] as T
}
fun <T> write(key: MemoryKey<in T>, value: T): Memory = Memory(data.put(key, value))
fun getEntries(): ImmutableMap<MemoryKey<*>, Any?> = data
}

private val variableIdSequence = AtomicLong()
open class MemoryKey<E>(val memoryType: MemoryType, val description: String = "var" + variableIdSequence.incrementAndGet()) {
override fun toString() = description
}

abstract class MemoryType {
abstract fun getMemory(state: VMState): Memory
abstract fun setMemory(state: VMState, memory: Memory): VMState

companion object {
val GLOBAL: MemoryType = object : MemoryType() {
override fun getMemory(state: VMState): Memory {
return state.globalMemory
}

override fun setMemory(state: VMState, memory: Memory): VMState {
return state.copy(globalMemory = memory)
}
}
val LOCAL: MemoryType = object : MemoryType() {
override fun getMemory(state: VMState): Memory {
return state.callStack.currentFrame().localMemory
}

override fun setMemory(state: VMState, memory: Memory): VMState {
return state.copy(callStack = state.callStack.updateCurrentFrame(state.callStack.currentFrame().copy(localMemory = memory)))
}
}
}
}

data class CallStack(val frames: PersistentList<StackFrame> = persistentListOf()) {
fun pushFrame(frame: StackFrame) = CallStack(frames + frame)
fun popFrame(): Pair<CallStack, StackFrame> = CallStack(frames.removeAt(frames.lastIndex)) to frames.last()
fun currentFrame() = frames.last()
fun updateCurrentFrame(newFrame: StackFrame) = CallStack(frames.set(frames.lastIndex, newFrame))
fun getFrames(): ImmutableList<StackFrame> = frames
fun size() = frames.size
}

data class StackFrame(
val returnTo: Instruction?,
val localMemory: Memory = Memory(),
val operandStack: PersistentList<Any?> = persistentListOf(),
) {
fun <T> writeLocalMemory(key: MemoryKey<T>, value: T): StackFrame = copy(localMemory = localMemory.write(key, value))
fun pushOperand(value: Any?): StackFrame = copy(operandStack = operandStack.add(value))
fun popOperand(): Pair<Any?, StackFrame> = operandStack.last() to copy(operandStack = operandStack.removeAt(operandStack.lastIndex))
}

abstract class Instruction {
var next: Instruction? = null
abstract fun execute(state: VMState): VMState
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package org.modelix.interpreter.vm.core

import kotlin.reflect.KProperty

class ProgramBuilder {

private val functions: MutableMap<Any, FunctionBuilder> = HashMap()

fun getFunction(key: Any): FunctionBuilder {
return checkNotNull(functions[key]) { "Function doesn't exist: $key" }
}

fun getOrBuildFunction(key: Any, body: FunctionBuilder.() -> Unit): FunctionBuilder {
return functions[key] ?: buildFunction(key, body)
}

fun buildFunction(key: Any, body: FunctionBuilder.() -> Unit): FunctionBuilder {
check(functions[key] == null) { "Function already exists: $key" }
val builder = FunctionBuilder()
functions[key] = builder
body(builder)
return builder
}

fun <T> variable(type: MemoryType = MemoryType.LOCAL) = O<T>(type)
}

class O<T>(private val type: MemoryType) {
private var instance: MemoryKey<T>? = null
operator fun getValue(thisRef: Nothing?, property: KProperty<*>): MemoryKey<T> {
return instance ?: MemoryKey<T>(type, property.name).also { instance = it }
}
}

class FunctionBuilder {
private var firstInstruction: Instruction? = null
private var lastInstruction: Instruction? = null

fun addInstruction(instruction: Instruction) {
lastInstruction?.next = instruction
lastInstruction = instruction
if (firstInstruction == null) {
firstInstruction = instruction
}
}

fun getEntryPoint(): Instruction {
return firstInstruction ?: NoOpInstruction().also { firstInstruction = it }
}

fun <T> load(value: T, variable: MemoryKey<T>) {
addInstruction(PushConstantInstruction(value))
addInstruction(StoreInstruction(variable))
}
}
Loading

0 comments on commit 308beda

Please sign in to comment.