diff --git a/.idea/.name b/.idea/.name
deleted file mode 100644
index 8f91b47..0000000
--- a/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-template
\ No newline at end of file
diff --git a/.idea/artifacts/mtmc_web_js_0_1_0_SNAPSHOT.xml b/.idea/artifacts/mtmc_web_js_0_1_0_SNAPSHOT.xml
new file mode 100644
index 0000000..a49e418
--- /dev/null
+++ b/.idea/artifacts/mtmc_web_js_0_1_0_SNAPSHOT.xml
@@ -0,0 +1,8 @@
+
+
+ $PROJECT_DIR$/build/libs
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/artifacts/mtmc_web_jvm_0_1_0_SNAPSHOT.xml b/.idea/artifacts/mtmc_web_jvm_0_1_0_SNAPSHOT.xml
new file mode 100644
index 0000000..7b2b661
--- /dev/null
+++ b/.idea/artifacts/mtmc_web_jvm_0_1_0_SNAPSHOT.xml
@@ -0,0 +1,8 @@
+
+
+ $PROJECT_DIR$/build/libs
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 7b907e9..2ac0fd5 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -3,7 +3,7 @@
-
+
diff --git a/build.gradle.kts b/build.gradle.kts
index 6b501dd..276852b 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,5 +1,6 @@
@file:OptIn(ExperimentalDistributionDsl::class)
+import org.codehaus.groovy.tools.shell.util.Logger.io
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalDistributionDsl
plugins {
@@ -50,6 +51,9 @@ kotlin {
implementation("nl.astraeus:simple-jdbc-stats:1.6.1") {
exclude(group = "org.slf4j", module = "slf4j-api")
}
+
+ implementation("io.pebbletemplates:pebble:3.2.3")
+ implementation("com.google.code.gson:gson:2.12.1")
}
}
val jvmTest by getting {
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 5b17425..9a9c5ba 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,5 +1,5 @@
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0"
}
-val REPO_NAME = "dummy so the gitea template compiles, please remove"
+
rootProject.name = "mtmc-web"
diff --git a/src/jsMain/kotlin/nl/astraeus/tmpl/Main.kt b/src/jsMain/kotlin/mtmc/Main.kt
similarity index 92%
rename from src/jsMain/kotlin/nl/astraeus/tmpl/Main.kt
rename to src/jsMain/kotlin/mtmc/Main.kt
index c141d33..be7f6c6 100644
--- a/src/jsMain/kotlin/nl/astraeus/tmpl/Main.kt
+++ b/src/jsMain/kotlin/mtmc/Main.kt
@@ -1,11 +1,10 @@
-package nl.astraeus.tmpl
+package mtmc
import kotlinx.browser.document
import kotlinx.html.div
import nl.astraeus.komp.HtmlBuilder
import nl.astraeus.komp.Komponent
-
class HelloKomponent : Komponent() {
override fun HtmlBuilder.render() {
div {
diff --git a/src/jvmMain/kotlin/nl/astraeus/tmpl/Main.kt b/src/jvmMain/kotlin/mtmc/Main.kt
similarity index 93%
rename from src/jvmMain/kotlin/nl/astraeus/tmpl/Main.kt
rename to src/jvmMain/kotlin/mtmc/Main.kt
index 74beed4..e69d885 100644
--- a/src/jvmMain/kotlin/nl/astraeus/tmpl/Main.kt
+++ b/src/jvmMain/kotlin/mtmc/Main.kt
@@ -1,4 +1,4 @@
-package nl.astraeus.tmpl
+package mtmc
import com.zaxxer.hikari.HikariConfig
import io.undertow.Undertow
@@ -8,8 +8,8 @@ import io.undertow.server.handlers.encoding.ContentEncodingRepository
import io.undertow.server.handlers.encoding.EncodingHandler
import io.undertow.server.handlers.encoding.GzipEncodingProvider
import nl.astraeus.logger.Logger
-import nl.astraeus.tmpl.db.Database
-import nl.astraeus.tmpl.web.RequestHandler
+import mtmc.db.Database
+import mtmc.web.RequestHandler
val log = Logger()
diff --git a/src/jvmMain/kotlin/mtmc/Placeholders.kt b/src/jvmMain/kotlin/mtmc/Placeholders.kt
new file mode 100644
index 0000000..762887d
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/Placeholders.kt
@@ -0,0 +1,8 @@
+package mtmc
+
+val SERVER_PORT = 4001
+val JDBC_PORT = 4002
+
+val pageTitle = "mtmc-web"
+val itemUrl = "mtmc-web"
+val repoName = "mtmc-web"
diff --git a/src/jvmMain/kotlin/mtmc/asm/ASMElement.kt b/src/jvmMain/kotlin/mtmc/asm/ASMElement.kt
new file mode 100644
index 0000000..9deb269
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/asm/ASMElement.kt
@@ -0,0 +1,19 @@
+package mtmc.asm
+
+import mtmc.tokenizer.MTMCToken
+
+abstract class ASMElement(
+ val labels: List,
+ @JvmField var lineNumber: Int
+) : HasLocation {
+ @JvmField
+ var errors: MutableList = ArrayList()
+ override var location: Int = 0
+ override var sizeInBytes: Int = 0
+
+ fun addError(token: MTMCToken, error: String) {
+ errors.add(ASMError(token, error))
+ }
+
+ abstract fun addError(integerValueRequired: String)
+}
diff --git a/src/jvmMain/kotlin/mtmc/asm/ASMError.kt b/src/jvmMain/kotlin/mtmc/asm/ASMError.kt
new file mode 100644
index 0000000..e6f692e
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/asm/ASMError.kt
@@ -0,0 +1,13 @@
+package mtmc.asm
+
+import mtmc.tokenizer.MTMCToken
+
+@JvmRecord
+data class ASMError(
+ @JvmField val token: MTMCToken,
+ @JvmField val error: String
+) {
+ fun formattedErrorMessage(): String {
+ return "Line " + token.line + ": " + error
+ }
+}
diff --git a/src/jvmMain/kotlin/mtmc/asm/Assembler.kt b/src/jvmMain/kotlin/mtmc/asm/Assembler.kt
new file mode 100644
index 0000000..67c06f8
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/asm/Assembler.kt
@@ -0,0 +1,798 @@
+package mtmc.asm
+
+import mtmc.asm.data.Data
+import mtmc.asm.graphics.Graphic
+import mtmc.asm.instructions.ALUInstruction
+import mtmc.asm.instructions.ALUOp.Companion.isALUOp
+import mtmc.asm.instructions.ErrorInstruction
+import mtmc.asm.instructions.Instruction
+import mtmc.asm.instructions.InstructionType
+import mtmc.asm.instructions.InstructionType.Companion.fromString
+import mtmc.asm.instructions.InstructionType.InstructionClass
+import mtmc.asm.instructions.JumpInstruction
+import mtmc.asm.instructions.JumpRegisterInstruction
+import mtmc.asm.instructions.LoadStoreInstruction
+import mtmc.asm.instructions.LoadStoreRegisterInstruction
+import mtmc.asm.instructions.MetaInstruction
+import mtmc.asm.instructions.MiscInstruction
+import mtmc.asm.instructions.StackInstruction
+import mtmc.asm.instructions.TestInstruction
+import mtmc.emulator.DebugInfo
+import mtmc.emulator.DebugInfo.GlobalInfo
+import mtmc.emulator.DebugInfo.LocalInfo
+import mtmc.emulator.MonTanaMiniComputer
+import mtmc.emulator.Register.Companion.isReadable
+import mtmc.emulator.Register.Companion.isWriteable
+import mtmc.os.SysCall
+import mtmc.os.exec.Executable
+import mtmc.tokenizer.MTMCToken
+import mtmc.tokenizer.MTMCTokenizer
+import java.io.File
+import java.nio.charset.StandardCharsets
+import java.util.*
+import java.util.stream.Collectors
+
+class Assembler {
+ var instructions: MutableList = ArrayList()
+ var instructionsSizeInBytes: Int = 0
+ var data: MutableList = ArrayList()
+ var graphics: MutableList = ArrayList()
+ var dataSize: Int = 0
+ var labels: HashMap
+
+ private var mode: ASMMode = ASMMode.TEXT
+ var tokenizer: MTMCTokenizer? = null
+ private var lastLabels: MutableList = mutableListOf()
+ private var srcName = "disk/file.asm"
+ private var debugStrings: MutableList = mutableListOf()
+
+ init {
+ labels = HashMap()
+ }
+
+ fun assemble(asm: String): AssemblyResult {
+ return assemble(null, asm)
+ }
+
+ fun assemble(file: String?, asm: String): AssemblyResult {
+ tokenizer = MTMCTokenizer(asm, "#")
+ debugStrings = ArrayList()
+ parseAssembly()
+ resolveLocations()
+ val errors = collectErrors()
+ var debugInfo: DebugInfo? = null
+ var code: ByteArray = ByteArray(0)
+ var data: ByteArray = ByteArray(0)
+ var graphics: Array = arrayOf()
+ if (errors.isEmpty()) {
+ code = genCode()
+ data = genData()
+ graphics = genGraphics()
+ debugInfo = genDebugInfo(file, asm, code)
+ }
+ return AssemblyResult(code, data, graphics, debugInfo, errors)
+ }
+
+ private fun genDebugInfo(
+ assemblyFile: String?,
+ assemblySource: String,
+ code: ByteArray
+ ): DebugInfo {
+ val assemblyLineNumbers = IntArray(code.size)
+
+ var originalFile: String = ""
+ val originalLineNumbers = IntArray(code.size)
+
+ val globals: MutableList = ArrayList()
+ val locals = Array>(code.size) { arrayOf() }
+
+ var location = 0
+ var originalLineNumber = 0
+ val currentLocals: MutableMap = TreeMap()
+ for (instruction in instructions) {
+ val asmLineNumber = instruction.lineNumber
+ if (instruction is MetaInstruction) {
+ if (instruction.isFileDirective) {
+ originalFile = instruction.getOriginalFilePath()!!
+ } else if (instruction.isLineDirective) {
+ originalLineNumber = instruction.originalLineNumber
+ } else if (instruction.isGlobalDirective) {
+ globals.add(
+ GlobalInfo(
+ instruction.getGlobalName(),
+ instruction.getGlobalLocation(),
+ instruction.getGlobalType()
+ )
+ )
+ } else if (instruction.isLocalDirective) {
+ currentLocals.put(
+ instruction.getLocalName(),
+ LocalInfo(
+ instruction.getLocalName(),
+ instruction.getLocalOffset(),
+ instruction.getLocalType()
+ )
+ )
+ } else if (instruction.isEndLocalDirective) {
+ currentLocals.remove(instruction.getLocalName())
+ }
+ }
+ while (location < instruction.location + instruction.sizeInBytes) {
+ assemblyLineNumbers[location] = asmLineNumber
+ originalLineNumbers[location] = originalLineNumber
+ locals[location] = currentLocals.values.toTypedArray()
+ location++
+ }
+ }
+
+ val debugInfo = DebugInfo(
+ debugStrings,
+ assemblyFile,
+ assemblySource,
+ assemblyLineNumbers,
+ originalFile,
+ originalLineNumbers,
+ globals.toTypedArray(),
+ locals
+ )
+
+ return debugInfo
+ }
+
+ fun assembleExecutable(srcName: String, asm: String): Executable {
+ this.srcName = "disk/" + srcName //TODO: The prefix should be replaced with FileSystem access
+
+ val result = assemble(srcName, asm)
+ if (!result.errors.isEmpty()) {
+ throw RuntimeException(
+ "Errors:\n" + result.errors
+ .stream()
+ .map { e: ASMError? -> " - " + e!!.formattedErrorMessage() }
+ .collect(Collectors.joining("\n")))
+ }
+
+ return Executable(
+ Executable.Format.Orc1,
+ result.code,
+ result.data,
+ result.graphics,
+ srcName,
+ result.debugInfo
+ )
+ }
+
+ private fun parseAssembly() {
+ while (tokenizer!!.more()) {
+ parseLine()
+ }
+ }
+
+ private fun genData(): ByteArray {
+ val dataBytes = ByteArray(dataSize)
+ for (dataElt in data) {
+ dataElt.genData(dataBytes, this)
+ }
+ return dataBytes
+ }
+
+ private fun genCode(): ByteArray {
+ val code = ByteArray(this.instructionsSizeInBytes)
+ for (instruction in instructions) {
+ instruction.genCode(code, this)
+ }
+ return code
+ }
+
+ private fun genOriginalLineNumbers(length: Int): IntArray {
+ val asmLineNumbers = IntArray(length)
+ var currentLineNumber = 0
+ var location = 0
+ for (instruction in instructions) {
+ if (instruction is MetaInstruction && instruction.isLineDirective) {
+ currentLineNumber = instruction.originalLineNumber
+ }
+ while (location < instruction.location + instruction.sizeInBytes) {
+ asmLineNumbers[location] = currentLineNumber
+ location++
+ }
+ }
+ return asmLineNumbers
+ }
+
+ private fun genGraphics(): Array {
+ val graphics = Array(this.graphics.size) { index ->
+ graphics[index].imageData
+ }
+
+ return graphics
+ }
+
+ private fun collectErrors(): MutableList {
+ val errors: MutableList = ArrayList()
+ for (instruction in instructions) {
+ errors.addAll(instruction.errors)
+ }
+ for (data in this.data) {
+ errors.addAll(data.errors)
+ }
+ for (graphic in this.graphics) {
+ errors.addAll(graphic.errors)
+ }
+ return errors
+ }
+
+ private fun resolveLocations() {
+ // assign locations
+ var offset = 0
+ for (instruction in instructions) {
+ instruction.location = offset
+ offset += instruction.sizeInBytes
+ this.instructionsSizeInBytes += instruction.sizeInBytes
+ }
+ for (data in data) {
+ data.location = offset
+ offset += data.sizeInBytes
+ dataSize += data.sizeInBytes
+ }
+ for (instruction in instructions) {
+ instruction.validateLabel(this)
+ }
+ }
+
+
+ private fun parseLine() {
+ val tokens = this.tokensForLine
+
+ if (parseMetaDirective(tokens)) {
+ return
+ }
+
+ val newMode = parseModeFlag(tokens)
+ if (newMode != null) {
+ mode = newMode
+ return
+ }
+ // label only lines are ok and propagate to the next instruction
+ val labelTokens: MutableList = maybeGetLabels(tokens)
+ val labels = ArrayList(lastLabels)
+ labels.addAll(labelTokens)
+ lastLabels = labels
+ if (tokens.isEmpty()) {
+ for (labelToken in labelTokens) {
+ val labelData = Data(labelTokens, labelToken.line)
+ if (hasLabel(labelToken.stringValue)) {
+ labelData.addError(tokens.poll(), "Label already defined: " + labelToken.stringValue)
+ } else {
+ this.labels.put(labelToken.labelValue(), labelData)
+ }
+ data.add(labelData)
+ }
+ } else if (mode == ASMMode.TEXT) {
+ parseInstruction(tokens, labels)
+ } else {
+ parseData(tokens, labels)
+ }
+ }
+
+ private fun parseMetaDirective(tokens: LinkedList): Boolean {
+ if (tokens.size >= 2 && tokens.get(0).type == MTMCToken.TokenType.AT) {
+ tokens.removeFirst()
+ val metaInstruction = MetaInstruction(tokens.removeFirst())
+ if (metaInstruction.isFileDirective) {
+ val path = requireString(tokens, metaInstruction)
+ metaInstruction.setOriginalFilePath(path)
+ } else if (metaInstruction.isGlobalDirective) {
+ val name = requireString(tokens, metaInstruction)
+ val location = requireIntegerToken(tokens, metaInstruction, Int.Companion.MAX_VALUE)
+ val type = requireString(tokens, metaInstruction)
+ metaInstruction.setGlobalInfo(name, location, type)
+ } else if (metaInstruction.isLocalDirective) {
+ val name = requireString(tokens, metaInstruction)
+ val offset = requireIntegerToken(tokens, metaInstruction, Int.Companion.MAX_VALUE)
+ val type = requireString(tokens, metaInstruction)
+ metaInstruction.setLocalInfo(name, offset, type)
+ } else if (metaInstruction.isEndLocalDirective) {
+ val name = requireString(tokens, metaInstruction)
+ metaInstruction.setEndLocalInfo(name)
+ } else if (metaInstruction.isLineDirective) {
+ val lineNumber = requireIntegerToken(tokens, metaInstruction, Int.Companion.MAX_VALUE)
+ metaInstruction.setOriginalLineNumber(lineNumber)
+ } else {
+ metaInstruction.addError("Unknown meta directive")
+ }
+ instructions.add(metaInstruction)
+ return true
+ }
+ return false
+ }
+
+ private fun parseModeFlag(tokens: LinkedList): ASMMode? {
+ if (tokens.size >= 2 && tokens.get(0).type == MTMCToken.TokenType.DOT && tokens.get(1).type == MTMCToken.TokenType.IDENTIFIER && tokens.get(
+ 0
+ ).end == tokens.get(1).start && (tokens.get(1).stringValue == "data" ||
+ tokens.get(1).stringValue == "text")
+ ) {
+ return ASMMode.valueOf(tokens.get(1).stringValue.uppercase(Locale.getDefault()))
+ }
+ return null
+ }
+
+ private fun parseData(tokens: LinkedList, labelTokens: MutableList) {
+ lastLabels = mutableListOf()
+ var dataToken = tokens.poll()
+ var dataElt = Data(labelTokens, if (dataToken == null) 0 else dataToken.line)
+ if (dataToken != null) {
+ if (dataToken.type == MTMCToken.TokenType.STRING) {
+ val stringBytes = dataToken.stringValue.toByteArray(StandardCharsets.US_ASCII)
+ val nullTerminated = ByteArray(stringBytes.size + 1)
+ System.arraycopy(stringBytes, 0, nullTerminated, 0, stringBytes.size)
+ nullTerminated[stringBytes.size] = '\u0000'.code.toByte()
+ dataElt.setValue(dataToken, nullTerminated)
+ } else if (isInteger(dataToken)) {
+ val integerValue = dataToken.intValue()
+ if (integerValue > Short.Companion.MAX_VALUE || integerValue < Short.Companion.MIN_VALUE) {
+ dataElt.addError(dataToken, "Number is too large")
+ }
+ dataElt.setValue(
+ dataToken,
+ byteArrayOf((integerValue ushr 8).toByte(), integerValue.toByte())
+ )
+ } else if (dataToken.type == MTMCToken.TokenType.DOT) {
+ dataToken = tokens.poll()
+ dataElt = Data(labelTokens, dataToken.line)
+ if (dataToken.stringValue == "int") {
+ val intToken = requireIntegerToken(tokens, dataElt, MonTanaMiniComputer.MEMORY_SIZE)
+ if (intToken != null) {
+ dataElt.setValue(intToken, ByteArray(intToken.intValue() * 2))
+ }
+ } else if (dataToken.stringValue == "byte") {
+ val intToken = requireIntegerToken(tokens, dataElt, MonTanaMiniComputer.MEMORY_SIZE)
+ if (intToken != null) {
+ dataElt.setValue(intToken, ByteArray(intToken.intValue()))
+ }
+ } else if (dataToken.stringValue == "image") {
+ val stringToken = requireString(tokens, dataElt)
+ if (stringToken != null) {
+ loadGraphic(labelTokens, dataElt, stringToken)
+ }
+ } else {
+ dataElt.addError(dataToken, "only data types are .int, .byte, and .image")
+ }
+ } else if (dataToken.type == MTMCToken.TokenType.MINUS) {
+ val nextToken = tokens.poll() // get next
+ if (nextToken == null || (nextToken.type != MTMCToken.TokenType.INTEGER && nextToken.type != MTMCToken.TokenType.HEX && nextToken.type != MTMCToken.TokenType.BINARY)) {
+ dataElt.addError(dataToken, "Number is too negative")
+ } else {
+ val integerValue = -1 * nextToken.intValue()
+ if (integerValue < Short.Companion.MIN_VALUE) {
+ dataElt.addError(dataToken, "Number is too negative")
+ }
+ val joinToken = MTMCToken.join(dataToken, nextToken, MTMCToken.TokenType.INTEGER)
+ dataElt.setValue(
+ joinToken,
+ byteArrayOf((integerValue ushr 8).toByte(), integerValue.toByte())
+ )
+ }
+ } else {
+ dataElt.addError(dataToken, "Unknown token type: " + dataToken.toString())
+ }
+ }
+
+ for (labelToken in labelTokens) {
+ if (hasLabel(labelToken.stringValue)) {
+ dataElt.addError(tokens.poll(), "Label already defined: " + labelToken.stringValue)
+ } else {
+ labels.put(labelToken.labelValue(), dataElt)
+ }
+ }
+ data.add(dataElt)
+ }
+
+ private fun loadGraphic(
+ labelTokens: MutableList,
+ data: Data,
+ token: MTMCToken
+ ): Graphic {
+ val graphic = Graphic(labelTokens, token.line)
+ val filename = token.stringValue
+ val file = File(File(this.srcName).getParent(), filename)
+ val index = graphics.size
+
+ data.setValue(token, byteArrayOf(((index shr 8) and 0xFF).toByte(), (index and 0xFF).toByte()))
+ graphic.setImage(file.getPath())
+ graphics.add(graphic)
+
+ return graphic
+ }
+
+ private fun parseInstruction(tokens: LinkedList, labelTokens: MutableList) {
+ var tokens = tokens
+ var instructionToken = tokens.peekFirst()
+ if (instructionToken == null) return
+
+ lastLabels = mutableListOf()
+ if (instructionToken.type != MTMCToken.TokenType.IDENTIFIER) {
+ instructions.add(ErrorInstruction(labelTokens, instructionToken, "Invalid Token"))
+ return
+ }
+
+ tokens = transformSyntheticInstructions(tokens)
+
+ instructionToken = tokens.poll()
+ val type = fromString(instructionToken.stringValue)
+
+ if (type == null) {
+ instructions.add(
+ ErrorInstruction(
+ labelTokens,
+ instructionToken,
+ "Unknown instruction token type: " + instructionToken.stringValue
+ )
+ )
+ return
+ }
+
+ val instruction: Instruction?
+ if (type.instructionClass == InstructionClass.MISC) {
+ val miscInst = MiscInstruction(type, labelTokens, instructionToken)
+ if (type == InstructionType.SYS) {
+ val sysCallType = requireSysCall(tokens, miscInst)
+ miscInst.setSyscallType(sysCallType)
+ } else if (type == InstructionType.MOV) {
+ val toRegister = requireWriteableRegister(tokens, miscInst)
+ miscInst.setTo(toRegister)
+ val fromRegister = requireReadableRegister(tokens, miscInst)
+ miscInst.setFrom(fromRegister)
+ } else if (type == InstructionType.INC || type == InstructionType.DEC) {
+ val toRegister = requireWriteableRegister(tokens, miscInst)
+ miscInst.setTo(toRegister)
+ if (!tokens.isEmpty()) {
+ val value = requireIntegerToken(tokens, miscInst, 15)
+ miscInst.setValue(value)
+ }
+ } else if (type == InstructionType.SETI) {
+ val toRegister = requireWriteableRegister(tokens, miscInst)
+ miscInst.setTo(toRegister)
+ val value = requireIntegerToken(tokens, miscInst, 15)
+ miscInst.setValue(value)
+ } else if (type == InstructionType.MCP) {
+ val fromRegister = requireWriteableRegister(tokens, miscInst)
+ miscInst.setFrom(fromRegister)
+ val toRegister = requireWriteableRegister(tokens, miscInst)
+ miscInst.setTo(toRegister)
+ val value = requireIntegerToken(tokens, miscInst, Short.Companion.MAX_VALUE.toInt())
+ miscInst.setValue(value)
+ } else if (type == InstructionType.DEBUG) {
+ val debugString = requireString(tokens, miscInst)
+ // create a dummy int token representing the offset of the debug string in the debug info
+ val debugStringIndex = debugStrings!!.size
+ debugStrings!!.add(debugString.stringValue)
+ val value = MTMCToken(0, 0, 0, 0, debugStringIndex.toString(), MTMCToken.TokenType.INTEGER)
+ miscInst.setValue(value)
+ }
+ instruction = miscInst
+ } else if (type.instructionClass == InstructionClass.ALU) {
+ val aluInst = ALUInstruction(type, labelTokens, instructionToken)
+ if (aluInst.isBinaryOp) {
+ val toRegister = requireWriteableRegister(tokens, aluInst)
+ aluInst.setTo(toRegister)
+ val fromRegister = requireReadableRegister(tokens, aluInst)
+ aluInst.setFrom(fromRegister)
+ } else if (aluInst.isImmediateOp()) {
+ val immediateOp = requireALUOp(tokens, aluInst)
+ // TODO - validate is max or lower op
+ aluInst.setImmediateOp(immediateOp)
+
+ val toRegister = requireWriteableRegister(tokens, aluInst)
+ aluInst.setTo(toRegister)
+
+ val value = requireIntegerToken(tokens, aluInst, Short.Companion.MAX_VALUE.toInt())
+ aluInst.setImmediateValue(value)
+ } else {
+ val toRegister = requireWriteableRegister(tokens, aluInst)
+ aluInst.setTo(toRegister)
+ }
+ instruction = aluInst
+ } else if (type.instructionClass == InstructionClass.STACK) {
+ val stackInst = StackInstruction(type, labelTokens, instructionToken)
+ if (type == InstructionType.PUSH) {
+ val targetRegister = requireReadableRegister(tokens, stackInst)
+ stackInst.setTarget(targetRegister)
+ } else if (type == InstructionType.POP) {
+ val toRegister = requireWriteableRegister(tokens, stackInst)
+ stackInst.setTarget(toRegister)
+ } else if (type == InstructionType.SOP) {
+ val aluOp = requireAluOp(tokens, stackInst)
+ stackInst.setALUOp(aluOp)
+ } else if (type == InstructionType.PUSHI) {
+ val value = requireIntegerToken(tokens, stackInst, Short.Companion.MAX_VALUE.toInt())
+ stackInst.setValue(value)
+ }
+
+ // if there is a stack register specified, consume it
+ if (!tokens.isEmpty() && tokens.peekFirst().type == MTMCToken.TokenType.IDENTIFIER) {
+ val stackReg = requireReadableRegister(tokens, stackInst)
+ stackInst.setStackRegister(stackReg)
+ }
+
+ instruction = stackInst
+ } else if (type.instructionClass == InstructionClass.TEST) {
+ val testInst = TestInstruction(type, labelTokens, instructionToken)
+ testInst.setFirst(requireReadableRegister(tokens, testInst))
+ if (testInst.isImmediate) {
+ testInst.setImmediateValue(requireIntegerToken(tokens, testInst, 15))
+ } else {
+ testInst.setSecond(requireReadableRegister(tokens, testInst))
+ }
+ instruction = testInst
+ } else if (type.instructionClass == InstructionClass.LOAD_STORE) {
+ val loadInst = LoadStoreInstruction(type, labelTokens, instructionToken)
+
+ val targetReg = requireReadableRegister(tokens, loadInst)
+ loadInst.setTargetToken(targetReg)
+
+ if (loadInst.isOffset) {
+ val offsetReg = requireReadableRegister(tokens, loadInst)
+ loadInst.setOffsetToken(offsetReg)
+ }
+
+ val value = requireIntegerOrLabelReferenceToken(tokens, loadInst)
+ loadInst.setValue(value)
+
+ instruction = loadInst
+ } else if (type.instructionClass == InstructionClass.LOAD_STORE_REGISTER) {
+ val loadInst = LoadStoreRegisterInstruction(type, labelTokens, instructionToken)
+
+ val targetReg = requireWriteableRegister(tokens, loadInst)
+ loadInst.setTargetToken(targetReg)
+
+ val pointerReg = requireWriteableRegister(tokens, loadInst)
+ loadInst.setPointerToken(pointerReg)
+
+ // if there is an offset register specified, consume it
+ if (!tokens.isEmpty() && tokens.peekFirst().type == MTMCToken.TokenType.IDENTIFIER) {
+ val offsetReg = requireReadableRegister(tokens, loadInst)
+ loadInst.setOffsetToken(offsetReg)
+ }
+ instruction = loadInst
+ } else if (type.instructionClass == InstructionClass.JUMP_REGISTER) {
+ val jumpInst = JumpRegisterInstruction(type, labelTokens, instructionToken)
+ val register = requireReadableRegister(tokens, jumpInst)
+ jumpInst.setRegister(register)
+ instruction = jumpInst
+ } else if (type.instructionClass == InstructionClass.JUMP) {
+ val jumpInst = JumpInstruction(type, labelTokens, instructionToken)
+ val labelValue: MTMCToken? = maybeGetLabelReference(tokens)
+ val valueToken: MTMCToken?
+ if (labelValue != null) {
+ valueToken = labelValue
+ } else {
+ valueToken = requireIntegerToken(tokens, jumpInst, MonTanaMiniComputer.MEMORY_SIZE)
+ }
+ jumpInst.setAddressToken(valueToken)
+ instruction = jumpInst
+ } else {
+ instruction = ErrorInstruction(
+ listOf(),
+ instructionToken,
+ "Unexpected Token"
+ )
+ }
+
+ for (labelToken in labelTokens) {
+ if (hasLabel(labelToken.stringValue)) {
+ instruction.addError(tokens.poll(), "Label already defined: " + labelToken.stringValue)
+ } else {
+ labels.put(labelToken.labelValue(), instruction)
+ }
+ }
+
+ // add error if any tokens are left on the line
+ if (!tokens.isEmpty()) {
+ instruction.addError(tokens.poll(), "Unexpected Token")
+ }
+
+ instructions.add(instruction)
+ }
+
+ //===================================================
+ // tokenization helper functions
+ //===================================================
+ private fun requireSysCall(tokens: LinkedList, inst: Instruction): MTMCToken {
+ val sysCallType = tokens.poll()
+ if (sysCallType == null) {
+ inst.addError("Syscall required")
+ } else if (sysCallType.type != MTMCToken.TokenType.IDENTIFIER) {
+ inst.addError(sysCallType, "Syscall required")
+ } else if (!SysCall.isSysCall(sysCallType.stringValue)) {
+ inst.addError(sysCallType, "Unknown syscall : " + sysCallType.stringValue)
+ }
+ return sysCallType!!
+ }
+
+ private fun requireAluOp(tokens: LinkedList, inst: Instruction): MTMCToken {
+ val sysCallType = tokens.poll()
+ if (sysCallType == null) {
+ inst.addError("Syscall required")
+ } else if (sysCallType.type != MTMCToken.TokenType.IDENTIFIER) {
+ inst.addError(sysCallType, "Syscall required")
+ } else if (!isALUOp(sysCallType.stringValue)) {
+ inst.addError(sysCallType, "Unknown alu operation : " + sysCallType.stringValue)
+ }
+ return sysCallType!!
+ }
+
+ private fun requireWriteableRegister(
+ tokens: LinkedList,
+ instruction: Instruction
+ ): MTMCToken {
+ val nextToken = tokens.poll()
+ if (nextToken == null) {
+ instruction.addError("Register required")
+ } else if (nextToken.type != MTMCToken.TokenType.IDENTIFIER) {
+ instruction.addError(nextToken, "Invalid Register : " + nextToken.stringValue)
+ } else if (!isWriteable(nextToken.stringValue)) {
+ instruction.addError(nextToken, "Register not writeable : " + nextToken.stringValue)
+ }
+ return nextToken!!
+ }
+
+ private fun requireReadableRegister(
+ tokens: LinkedList,
+ instruction: Instruction
+ ): MTMCToken {
+ val nextToken = tokens.poll()
+ if (nextToken == null) {
+ instruction.addError("Register required")
+ } else if (nextToken.type != MTMCToken.TokenType.IDENTIFIER) {
+ instruction.addError(nextToken, "Invalid Register : " + nextToken.stringValue)
+ } else if (!isReadable(nextToken.stringValue)) {
+ instruction.addError(nextToken, "Register not readable : " + nextToken.stringValue)
+ }
+ return nextToken!!
+ }
+
+ private fun requireALUOp(tokens: LinkedList, instruction: Instruction): MTMCToken {
+ val nextToken = tokens.poll()
+ if (nextToken == null || nextToken.type != MTMCToken.TokenType.IDENTIFIER || !isALUOp(nextToken.stringValue)) {
+ instruction.addError("ALU operation required")
+ }
+ return nextToken!!
+ }
+
+ private fun requireString(tokens: LinkedList, instruction: ASMElement): MTMCToken {
+ val nextToken = tokens.poll()
+ if (nextToken == null || nextToken.type != MTMCToken.TokenType.STRING) {
+ instruction.addError("String required")
+ }
+ return nextToken!!
+ }
+
+ private fun requireIntegerToken(
+ tokens: LinkedList,
+ inst: ASMElement,
+ max: Int
+ ): MTMCToken {
+ val token = tokens.poll()
+ if (token == null) {
+ inst.addError("Integer value required")
+ } else if (isInteger(token)) {
+ val integerValue = token.intValue()
+ if (integerValue < 0 || max < integerValue) {
+ inst.addError(token, "Integer value out of range: 0-" + max)
+ }
+ } else {
+ inst.addError(token, "Integer value expected")
+ }
+ return token!!
+ }
+
+ private fun requireToken(
+ tokens: LinkedList,
+ type: MTMCToken.TokenType,
+ inst: ASMElement
+ ): MTMCToken? {
+ val token = tokens.poll()
+ if (token == null || token.type != type) {
+ inst.addError(token, "Token " + type.name + " required")
+ }
+ return token
+ }
+
+ private fun requireIntegerOrLabelReferenceToken(
+ tokens: LinkedList,
+ inst: LoadStoreInstruction
+ ): MTMCToken {
+ val token = tokens.poll()
+ if (token == null) {
+ inst.addError("Integer or label value required")
+ } else if (!isInteger(token) && token.type != MTMCToken.TokenType.IDENTIFIER) {
+ inst.addError(token, "Integer or label value expected")
+ }
+ return token!!
+ }
+
+
+ private val tokensForLine: LinkedList
+ get() {
+ val tokens = LinkedList()
+ if (tokenizer!!.more()) {
+ val first = tokenizer!!.consume()
+ tokens.add(first)
+ while (tokenizer!!.more() &&
+ first.line == tokenizer!!.currentToken().line
+ ) {
+ tokens.add(tokenizer!!.consume())
+ }
+ }
+ return tokens
+ }
+
+ fun hasLabel(label: String?): Boolean {
+ return labels.containsKey(label)
+ }
+
+ fun resolveLabel(label: String?): Int {
+ return labels.get(label)!!.location
+ }
+
+ internal enum class ASMMode {
+ DATA,
+ TEXT
+ }
+
+ companion object {
+ fun transformSyntheticInstructions(tokens: LinkedList): LinkedList {
+ if (!tokens.isEmpty()) {
+ val first = tokens.peekFirst()
+ if (first.type == MTMCToken.TokenType.IDENTIFIER) {
+ val stringVal = first.stringValue
+ if (stringVal.endsWith("i")) {
+ val op = stringVal.substring(0, stringVal.length - 1)
+ if (isALUOp(op)) {
+ val syntheticImmediate = tokens.removeFirst()
+ tokens.addFirst(syntheticImmediate.cloneWithVal(op))
+ tokens.addFirst(syntheticImmediate.cloneWithVal("imm"))
+ }
+ } else if (stringVal.startsWith("s")) {
+ val op = stringVal.substring(1, stringVal.length)
+ if (isALUOp(op)) {
+ val syntheticImmediate = tokens.removeFirst()
+ tokens.addFirst(syntheticImmediate.cloneWithVal(op))
+ tokens.addFirst(syntheticImmediate.cloneWithVal("sop"))
+ }
+ } else if (stringVal == "la") {
+ val syntheticImmediate = tokens.removeFirst()
+ tokens.addFirst(syntheticImmediate.cloneWithVal("li"))
+ } else if (stringVal == "ret") {
+ val syntheticImmediate = tokens.removeFirst()
+ tokens.addFirst(syntheticImmediate.cloneWithVal("ra"))
+ tokens.addFirst(syntheticImmediate.cloneWithVal("jr"))
+ }
+ }
+ }
+ return tokens
+ }
+
+ private fun maybeGetLabels(tokens: LinkedList): MutableList {
+ val labels = LinkedList()
+ while (!tokens.isEmpty() && tokens.getFirst().type == MTMCToken.TokenType.LABEL) {
+ val label = tokens.poll()
+ labels.add(label!!)
+ }
+ return labels
+ }
+
+ private fun maybeGetLabelReference(tokens: LinkedList): MTMCToken? {
+ var label: MTMCToken? = null
+ if (tokens.getFirst().type == MTMCToken.TokenType.IDENTIFIER) {
+ label = tokens.poll()
+ }
+ return label
+ }
+
+ private fun isInteger(token: MTMCToken?): Boolean {
+ return token != null && (token.type == MTMCToken.TokenType.INTEGER || token.type == MTMCToken.TokenType.HEX || token.type == MTMCToken.TokenType.BINARY)
+ }
+ }
+}
diff --git a/src/jvmMain/kotlin/mtmc/asm/AssemblyResult.kt b/src/jvmMain/kotlin/mtmc/asm/AssemblyResult.kt
new file mode 100644
index 0000000..38e88d8
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/asm/AssemblyResult.kt
@@ -0,0 +1,20 @@
+package mtmc.asm
+
+import mtmc.emulator.DebugInfo
+
+@JvmRecord
+data class AssemblyResult(
+ @JvmField val code: ByteArray,
+ val data: ByteArray,
+ val graphics: Array,
+ @JvmField val debugInfo: DebugInfo?,
+ @JvmField val errors: MutableList
+) {
+ fun printErrors(): String {
+ val builder = StringBuilder("Errors:\n")
+ for (error in errors) {
+ builder.append(" Line " + error.token.line + ": " + error.error).append('\n')
+ }
+ return builder.toString()
+ }
+}
diff --git a/src/jvmMain/kotlin/mtmc/asm/HasLocation.kt b/src/jvmMain/kotlin/mtmc/asm/HasLocation.kt
new file mode 100644
index 0000000..11d4b43
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/asm/HasLocation.kt
@@ -0,0 +1,6 @@
+package mtmc.asm
+
+interface HasLocation {
+ var location: Int
+ var sizeInBytes: Int
+}
diff --git a/src/jvmMain/kotlin/mtmc/asm/data/Data.kt b/src/jvmMain/kotlin/mtmc/asm/data/Data.kt
new file mode 100644
index 0000000..70471d9
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/asm/data/Data.kt
@@ -0,0 +1,37 @@
+package mtmc.asm.data
+
+import mtmc.asm.ASMElement
+import mtmc.asm.Assembler
+import mtmc.tokenizer.MTMCToken
+
+class Data(labels: MutableList, lineNumber: Int) : ASMElement(labels, lineNumber) {
+ var valueToken: MTMCToken? = null
+ private var value: ByteArray = ByteArray(0)
+
+ override var sizeInBytes: Int
+ get() = value.size
+ set(value) {}
+
+ fun genData(dataBytes: ByteArray, assembler: Assembler) {
+ val offset = location - assembler.instructionsSizeInBytes
+ for (i in 0.., lineNumber: Int) : ASMElement(labels, lineNumber) {
+ private var filename: String? = null
+ var imageData: ByteArray = ByteArray(0)
+
+ fun setImage(filename: String) {
+ try {
+ var image = ImageIO.read(File(filename))
+ val buffer = ByteArrayOutputStream()
+
+ if (image.getWidth() > 1024 || image.getHeight() > 1024) {
+ addError(filename + " is too large. Maximum image size is 1024x1024")
+ }
+
+ image = convertImage(image)
+
+ ImageIO.write(image, "png", buffer)
+
+ this.filename = filename
+ this.imageData = buffer.toByteArray()
+ } catch (e: FileNotFoundException) {
+ e.printStackTrace()
+ addError("$filename not found")
+ } catch (e: IOException) {
+ e.printStackTrace()
+ addError(e.message ?: "error in Graphic") // TODO: Verify these messages are meaningful
+ }
+ }
+
+ override fun addError(err: String) {
+ addError(labels.last(), err)
+ }
+
+ override var sizeInBytes: Int
+ get() = 2
+ set(value) {}
+}
diff --git a/src/jvmMain/kotlin/mtmc/asm/instructions/ALUInstruction.kt b/src/jvmMain/kotlin/mtmc/asm/instructions/ALUInstruction.kt
new file mode 100644
index 0000000..d70534f
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/asm/instructions/ALUInstruction.kt
@@ -0,0 +1,89 @@
+package mtmc.asm.instructions
+
+import mtmc.asm.Assembler
+import mtmc.emulator.Register.Companion.fromInteger
+import mtmc.emulator.Register.Companion.toInteger
+import mtmc.tokenizer.MTMCToken
+import mtmc.util.BinaryUtils
+import java.util.Locale
+
+class ALUInstruction(
+ type: InstructionType,
+ label: List,
+ instructionToken: MTMCToken
+) : Instruction(type, label, instructionToken) {
+ private var toToken: MTMCToken? = null
+ private var fromToken: MTMCToken? = null
+ private var immediateOp: MTMCToken? = null
+ private var value: MTMCToken? = null
+
+ fun setTo(to: MTMCToken) {
+ this.toToken = to
+ }
+
+ fun setFrom(from: MTMCToken?) {
+ this.fromToken = from
+ }
+
+ fun setImmediateValue(value: MTMCToken) {
+ this.value = value
+ }
+
+ fun setImmediateOp(immediateOp: MTMCToken) {
+ this.immediateOp = immediateOp
+ }
+
+ fun isImmediateOp(): Boolean {
+ return this.type == InstructionType.IMM
+ }
+
+ val isBinaryOp: Boolean
+ get() = !ALUOp.valueOf(instructionToken.stringValue.uppercase(Locale.getDefault())).isUnary
+
+ override fun genCode(output: ByteArray, assembler: Assembler) {
+ val opCode = ALUOp.toInteger(instructionToken.stringValue)
+ val to = toInteger(toToken!!.stringValue)
+ var from = 0
+ if (fromToken != null) {
+ from = toInteger(fromToken!!.stringValue)
+ }
+ output[location] = (16 or opCode).toByte()
+ if (this.isBinaryOp) {
+ output[location + 1] = (to shl 4 or from).toByte()
+ } else if (isImmediateOp()) {
+ val immediateValue = value!!.intValue()
+ val immediateOpValue = ALUOp.toInteger(immediateOp!!.stringValue)
+ output[location + 1] = (to shl 4 or immediateOpValue).toByte()
+ output[location + 2] = (immediateValue ushr 8).toByte()
+ output[location + 3] = immediateValue.toByte()
+ } else { // unary op
+ output[location + 1] = (to shl 4).toByte()
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ fun disassemble(instruction: Short): String? {
+ if (BinaryUtils.getBits(16, 4, instruction).toInt() == 1) {
+ val builder = StringBuilder()
+ val opCode = BinaryUtils.getBits(12, 4, instruction)
+ val op = ALUOp.fromInt(opCode)
+ val aluOp = ALUOp.valueOf(op.uppercase(Locale.getDefault()))
+ if (aluOp == ALUOp.IMM) {
+ builder.append(op).append(" ")
+ builder.append(fromInteger(BinaryUtils.getBits(8, 4, instruction).toInt())).append(" ")
+ builder.append(ALUOp.fromInt(BinaryUtils.getBits(4, 4, instruction))).append(" ")
+ } else if (aluOp.isUnary) {
+ builder.append(op).append(" ")
+ builder.append(fromInteger(BinaryUtils.getBits(8, 4, instruction).toInt())).append(" ")
+ } else {
+ builder.append(op).append(" ")
+ builder.append(fromInteger(BinaryUtils.getBits(8, 4, instruction).toInt())).append(" ")
+ builder.append(fromInteger(BinaryUtils.getBits(4, 4, instruction).toInt())).append(" ")
+ }
+ return builder.toString()
+ }
+ return null
+ }
+ }
+}
diff --git a/src/jvmMain/kotlin/mtmc/asm/instructions/ALUOp.kt b/src/jvmMain/kotlin/mtmc/asm/instructions/ALUOp.kt
new file mode 100644
index 0000000..821897f
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/asm/instructions/ALUOp.kt
@@ -0,0 +1,44 @@
+package mtmc.asm.instructions
+
+import java.util.*
+
+enum class ALUOp(val opCode: Int, val isUnary: Boolean) {
+ ADD(0x0000, false),
+ SUB(0x0001, false),
+ MUL(0x0002, false),
+ DIV(0x0003, false),
+ MOD(0x0004, false),
+ AND(0x0005, false),
+ OR(0x0006, false),
+ XOR(0x0007, false),
+ SHL(0x0008, false),
+ SHR(0x0009, false),
+ MIN(0x000A, false),
+ MAX(0x000B, false),
+ NOT(0x000C, true),
+ LNOT(0x000D, true),
+ NEG(0x000E, true),
+ IMM(0x000F, true);
+
+ companion object {
+ @JvmStatic
+ fun toInteger(instruction: String): Int {
+ return valueOf(instruction.uppercase(Locale.getDefault())).opCode
+ }
+
+ @JvmStatic
+ fun fromInt(opCode: Short): String {
+ return entries[opCode.toInt()].name.lowercase(Locale.getDefault())
+ }
+
+ @JvmStatic
+ fun isALUOp(op: String): Boolean {
+ try {
+ val aluOp = valueOf(op.uppercase(Locale.getDefault()))
+ return true
+ } catch (e: IllegalArgumentException) {
+ return false
+ }
+ }
+ }
+}
diff --git a/src/jvmMain/kotlin/mtmc/asm/instructions/ErrorInstruction.kt b/src/jvmMain/kotlin/mtmc/asm/instructions/ErrorInstruction.kt
new file mode 100644
index 0000000..3427536
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/asm/instructions/ErrorInstruction.kt
@@ -0,0 +1,23 @@
+package mtmc.asm.instructions
+
+import mtmc.asm.Assembler
+import mtmc.tokenizer.MTMCToken
+
+class ErrorInstruction(
+ labels: List,
+ instruction: MTMCToken,
+ error: String
+) : Instruction(
+ InstructionType.ERROR,
+ labels,
+ instruction
+) {
+
+ init {
+ addError(instruction, error)
+ }
+
+ override fun genCode(output: ByteArray, assembler: Assembler) {
+ // do nothing
+ }
+}
diff --git a/src/jvmMain/kotlin/mtmc/asm/instructions/Instruction.kt b/src/jvmMain/kotlin/mtmc/asm/instructions/Instruction.kt
new file mode 100644
index 0000000..0f08b63
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/asm/instructions/Instruction.kt
@@ -0,0 +1,72 @@
+package mtmc.asm.instructions
+
+import mtmc.asm.ASMElement
+import mtmc.asm.Assembler
+import mtmc.asm.instructions.ALUInstruction.Companion.disassemble
+import mtmc.emulator.MonTanaMiniComputer.Companion.isDoubleWordInstruction
+import mtmc.tokenizer.MTMCToken
+
+abstract class Instruction(
+ @JvmField val type: InstructionType,
+ labels: List,
+ @JvmField val instructionToken: MTMCToken
+) : ASMElement(labels, instructionToken.line) {
+ open fun validateLabel(assembler: Assembler) {
+ // default does nothing
+ }
+
+ override fun addError(error: String) {
+ addError(instructionToken, error)
+ }
+
+ abstract fun genCode(output: ByteArray, assembler: Assembler)
+
+ override var sizeInBytes: Int = type.sizeInBytes
+ get() = type.sizeInBytes
+
+ companion object {
+ @JvmStatic
+ fun isInstruction(cmd: String): Boolean {
+ return InstructionType.fromString(cmd) != null
+ }
+
+ fun disassemble(instruction: Short, previousInstruction: Short): String {
+ if (isDoubleWordInstruction(previousInstruction)) {
+ return instruction.toInt().toString()
+ }
+ val misc = MiscInstruction.disassemble(instruction)
+ if (misc != null) {
+ return misc
+ }
+ val aluOp = disassemble(instruction)
+ if (aluOp != null) {
+ return aluOp
+ }
+ val stack = StackInstruction.disassemble(instruction)
+ if (stack != null) {
+ return stack
+ }
+ val test = TestInstruction.disassemble(instruction)
+ if (test != null) {
+ return test
+ }
+ val lsr = LoadStoreRegisterInstruction.disassemble(instruction)
+ if (lsr != null) {
+ return lsr
+ }
+ val ls = LoadStoreInstruction.disassemble(instruction)
+ if (ls != null) {
+ return ls
+ }
+ val jumpReg = JumpInstruction.disassemble(instruction)
+ if (jumpReg != null) {
+ return jumpReg
+ }
+ val jump = JumpInstruction.disassemble(instruction)
+ if (jump != null) {
+ return jump
+ }
+ return ""
+ }
+ }
+}
diff --git a/src/jvmMain/kotlin/mtmc/asm/instructions/InstructionType.kt b/src/jvmMain/kotlin/mtmc/asm/instructions/InstructionType.kt
new file mode 100644
index 0000000..5267bdb
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/asm/instructions/InstructionType.kt
@@ -0,0 +1,98 @@
+package mtmc.asm.instructions
+
+import java.util.*
+
+enum class InstructionType @JvmOverloads constructor(
+ @JvmField val instructionClass: InstructionClass?,
+ val sizeInBytes: Int = 2
+) {
+ SYS(InstructionClass.MISC),
+ MOV(InstructionClass.MISC),
+ INC(InstructionClass.MISC),
+ DEC(InstructionClass.MISC),
+ SETI(InstructionClass.MISC),
+ NOP(InstructionClass.MISC),
+ MCP(InstructionClass.MISC, 4),
+ DEBUG(InstructionClass.MISC),
+ ADD(InstructionClass.ALU),
+ SUB(InstructionClass.ALU),
+ MUL(InstructionClass.ALU),
+ DIV(InstructionClass.ALU),
+ MOD(InstructionClass.ALU),
+ AND(InstructionClass.ALU),
+ OR(InstructionClass.ALU),
+ XOR(InstructionClass.ALU),
+ SHL(InstructionClass.ALU),
+ SHR(InstructionClass.ALU),
+ MIN(InstructionClass.ALU),
+ MAX(InstructionClass.ALU),
+ NOT(InstructionClass.ALU),
+ LNOT(InstructionClass.ALU),
+ NEG(InstructionClass.ALU),
+ IMM(InstructionClass.ALU, 4),
+ PUSH(InstructionClass.STACK),
+ POP(InstructionClass.STACK),
+ DUP(InstructionClass.STACK),
+ SWAP(InstructionClass.STACK),
+ DROP(InstructionClass.STACK),
+ OVER(InstructionClass.STACK),
+ ROT(InstructionClass.STACK),
+ SOP(InstructionClass.STACK),
+ PUSHI(InstructionClass.STACK, 4),
+ EQ(InstructionClass.TEST),
+ NEQ(InstructionClass.TEST),
+ GT(InstructionClass.TEST),
+ GTE(InstructionClass.TEST),
+ LT(InstructionClass.TEST),
+ LTE(InstructionClass.TEST),
+ EQI(InstructionClass.TEST),
+ NEQI(InstructionClass.TEST),
+ GTI(InstructionClass.TEST),
+ GTEI(InstructionClass.TEST),
+ LTI(InstructionClass.TEST),
+ LTEI(InstructionClass.TEST),
+ LWR(InstructionClass.LOAD_STORE_REGISTER),
+ LBR(InstructionClass.LOAD_STORE_REGISTER),
+ SWR(InstructionClass.LOAD_STORE_REGISTER),
+ SBR(InstructionClass.LOAD_STORE_REGISTER),
+ LW(InstructionClass.LOAD_STORE, 4),
+ LWO(InstructionClass.LOAD_STORE, 4),
+ LI(InstructionClass.LOAD_STORE, 4),
+ LB(InstructionClass.LOAD_STORE, 4),
+ LBO(InstructionClass.LOAD_STORE, 4),
+ SW(InstructionClass.LOAD_STORE, 4),
+ SWO(InstructionClass.LOAD_STORE, 4),
+ SB(InstructionClass.LOAD_STORE, 4),
+ SBO(InstructionClass.LOAD_STORE, 4),
+ JR(InstructionClass.JUMP_REGISTER),
+ J(InstructionClass.JUMP),
+ JZ(InstructionClass.JUMP),
+ JNZ(InstructionClass.JUMP),
+ JAL(InstructionClass.JUMP),
+ META(InstructionClass.MISC),
+ ERROR(InstructionClass.MISC),
+ ;
+
+
+ enum class InstructionClass {
+ MISC,
+ ALU,
+ STACK,
+ TEST,
+ LOAD_STORE_REGISTER,
+ LOAD_STORE,
+ JUMP_REGISTER,
+ JUMP
+ }
+
+ companion object {
+ @JvmStatic
+ fun fromString(string: String): InstructionType? {
+ try {
+ return valueOf(string.uppercase(Locale.getDefault()))
+ } catch (e: IllegalArgumentException) {
+ return null
+ }
+ }
+ }
+}
diff --git a/src/jvmMain/kotlin/mtmc/asm/instructions/JumpInstruction.kt b/src/jvmMain/kotlin/mtmc/asm/instructions/JumpInstruction.kt
new file mode 100644
index 0000000..d784bde
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/asm/instructions/JumpInstruction.kt
@@ -0,0 +1,69 @@
+package mtmc.asm.instructions
+
+import mtmc.asm.Assembler
+import mtmc.tokenizer.MTMCToken
+import mtmc.util.BinaryUtils
+
+class JumpInstruction(
+ type: InstructionType,
+ label: List,
+ instructionToken: MTMCToken
+) : Instruction(type, label, instructionToken) {
+ private var addressToken: MTMCToken? = null
+
+ fun setAddressToken(addressToken: MTMCToken) {
+ this.addressToken = addressToken
+ }
+
+ override fun validateLabel(assembler: Assembler) {
+ if (addressToken!!.type == MTMCToken.TokenType.IDENTIFIER) {
+ if (!assembler.hasLabel(addressToken!!.stringValue)) {
+ addError("Unresolved label: " + addressToken!!.stringValue)
+ }
+ }
+ }
+
+ private fun resolveTargetAddress(assembler: Assembler): Int? {
+ if (addressToken!!.type == MTMCToken.TokenType.IDENTIFIER) {
+ return assembler.resolveLabel(addressToken!!.stringValue)
+ } else {
+ return addressToken!!.intValue()
+ }
+ }
+
+ override fun genCode(output: ByteArray, assembler: Assembler) {
+ var opcode = 0
+ when (type) {
+ InstructionType.J -> opcode = 12
+ InstructionType.JZ -> opcode = 13
+ InstructionType.JNZ -> opcode = 14
+ InstructionType.JAL -> opcode = 15
+ else -> println("Invalid instruction type")
+ }
+ val address = resolveTargetAddress(assembler)!!
+ output[location] = (opcode shl 4 or (address ushr 8)).toByte()
+ output[location + 1] = address.toByte()
+ }
+
+ companion object {
+ fun disassemble(instruction: Short): String? {
+ if (BinaryUtils.getBits(16, 2, instruction).toInt() == 3) {
+ val jumpType = BinaryUtils.getBits(14, 2, instruction)
+ val sb = StringBuilder()
+ if (jumpType.toInt() == 0) {
+ sb.append("j")
+ } else if (jumpType.toInt() == 1) {
+ sb.append("jz")
+ } else if (jumpType.toInt() == 2) {
+ sb.append("jnz")
+ } else if (jumpType.toInt() == 3) {
+ sb.append("jal")
+ }
+ val target = BinaryUtils.getBits(12, 12, instruction)
+ sb.append(" ").append(target.toInt())
+ return sb.toString()
+ }
+ return null
+ }
+ }
+}
diff --git a/src/jvmMain/kotlin/mtmc/asm/instructions/JumpRegisterInstruction.kt b/src/jvmMain/kotlin/mtmc/asm/instructions/JumpRegisterInstruction.kt
new file mode 100644
index 0000000..7e8596f
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/asm/instructions/JumpRegisterInstruction.kt
@@ -0,0 +1,37 @@
+package mtmc.asm.instructions
+
+import mtmc.asm.Assembler
+import mtmc.emulator.Register.Companion.fromInteger
+import mtmc.emulator.Register.Companion.toInteger
+import mtmc.tokenizer.MTMCToken
+import mtmc.util.BinaryUtils
+
+class JumpRegisterInstruction(
+ type: InstructionType,
+ label: List,
+ instructionToken: MTMCToken
+) : Instruction(type, label, instructionToken) {
+ private var register: MTMCToken? = null
+
+ fun setRegister(register: MTMCToken) {
+ this.register = register
+ }
+
+ public override fun genCode(output: ByteArray, assembler: Assembler) {
+ val opcode = 9
+ output[location] = (opcode shl 4).toByte()
+ val reg = toInteger(register!!.stringValue)
+ output[location + 1] = reg.toByte()
+ }
+
+ companion object {
+ fun disassemble(instruction: Short): String? {
+ if (BinaryUtils.getBits(16, 5, instruction).toInt() == 9) {
+ val reg = BinaryUtils.getBits(4, 4, instruction)
+ val sb = StringBuilder("jr")
+ sb.append(fromInteger(reg.toInt()))
+ }
+ return null
+ }
+ }
+}
diff --git a/src/jvmMain/kotlin/mtmc/asm/instructions/LoadStoreInstruction.kt b/src/jvmMain/kotlin/mtmc/asm/instructions/LoadStoreInstruction.kt
new file mode 100644
index 0000000..3eff67a
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/asm/instructions/LoadStoreInstruction.kt
@@ -0,0 +1,115 @@
+package mtmc.asm.instructions
+
+import mtmc.asm.Assembler
+import mtmc.emulator.Register.Companion.fromInteger
+import mtmc.emulator.Register.Companion.toInteger
+import mtmc.tokenizer.MTMCToken
+import mtmc.util.BinaryUtils
+
+class LoadStoreInstruction(
+ type: InstructionType,
+ label: List,
+ instructionToken: MTMCToken
+) : Instruction(type, label, instructionToken) {
+ private var targetToken: MTMCToken? = null
+ private var offsetToken: MTMCToken? = null
+ private var value: MTMCToken? = null
+
+ fun setTargetToken(targetToken: MTMCToken) {
+ this.targetToken = targetToken
+ }
+
+ fun setOffsetToken(offsetToken: MTMCToken) {
+ this.offsetToken = offsetToken
+ }
+
+ fun setValue(value: MTMCToken) {
+ this.value = value
+ }
+
+ val isOffset: Boolean
+ get() = type.name.endsWith("O")
+
+ public override fun validateLabel(assembler: Assembler) {
+ if (value!!.type == MTMCToken.TokenType.IDENTIFIER) {
+ if (!assembler.hasLabel(value!!.stringValue)) {
+ addError("Unresolved label: " + value!!.stringValue)
+ }
+ }
+ }
+
+ private fun resolveValue(assembler: Assembler): Int? {
+ if (value!!.type == MTMCToken.TokenType.IDENTIFIER) {
+ return assembler.resolveLabel(value!!.stringValue)
+ } else {
+ return value!!.intValue()
+ }
+ }
+
+ override fun genCode(output: ByteArray, assembler: Assembler) {
+ val upperByte = when (type) {
+ InstructionType.LW -> 128
+ InstructionType.LWO -> 129
+ InstructionType.LB -> 130
+ InstructionType.LBO -> 131
+ InstructionType.SW -> 132
+ InstructionType.SWO -> 133
+ InstructionType.SB -> 134
+ InstructionType.SBO -> 135
+ InstructionType.LI -> 143
+ else -> 0
+ }
+
+ val target = toInteger(targetToken!!.stringValue)
+ output[location] = upperByte.toByte()
+
+ if (this.isOffset) {
+ val offsetReg = toInteger(offsetToken!!.stringValue)
+ output[location + 1] = (target shl 4 or offsetReg).toByte()
+ } else {
+ output[location + 1] = (target shl 4).toByte()
+ }
+
+ val numericValue = resolveValue(assembler)!!
+ output[location + 2] = (numericValue ushr 8).toByte()
+ output[location + 3] = numericValue.toByte()
+ }
+
+ companion object {
+ fun disassemble(instruction: Short): String? {
+ if (BinaryUtils.getBits(16, 4, instruction).toInt() == 8) {
+ val topNibble = BinaryUtils.getBits(12, 4, instruction)
+ val sb = StringBuilder()
+ if (topNibble.toInt() == 15) {
+ sb.append("li ")
+ } else if (topNibble.toInt() == 0) {
+ sb.append("lw ")
+ } else if (topNibble.toInt() == 1) {
+ sb.append("lwo ")
+ } else if (topNibble.toInt() == 2) {
+ sb.append("lb ")
+ } else if (topNibble.toInt() == 3) {
+ sb.append("lbo ")
+ } else if (topNibble.toInt() == 4) {
+ sb.append("sw ")
+ } else if (topNibble.toInt() == 5) {
+ sb.append("swo ")
+ } else if (topNibble.toInt() == 6) {
+ sb.append("sb ")
+ } else if (topNibble.toInt() == 7) {
+ sb.append("sbo ")
+ }
+ val target = BinaryUtils.getBits(8, 4, instruction)
+ val reg = fromInteger(target.toInt())
+ sb.append(reg)
+ if (topNibble.toInt() == 1 || topNibble.toInt() == 3 || topNibble.toInt() == 5 || topNibble.toInt() == 7) {
+ val offset = BinaryUtils.getBits(4, 4, instruction)
+ val offsetReg = fromInteger(offset.toInt())
+ sb.append(" ").append(offsetReg)
+ }
+ return sb.toString()
+ }
+ return null
+ }
+ }
+}
diff --git a/src/jvmMain/kotlin/mtmc/asm/instructions/LoadStoreRegisterInstruction.kt b/src/jvmMain/kotlin/mtmc/asm/instructions/LoadStoreRegisterInstruction.kt
new file mode 100644
index 0000000..2778b52
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/asm/instructions/LoadStoreRegisterInstruction.kt
@@ -0,0 +1,75 @@
+package mtmc.asm.instructions
+
+import mtmc.asm.Assembler
+import mtmc.emulator.Register
+import mtmc.emulator.Register.Companion.fromInteger
+import mtmc.emulator.Register.Companion.toInteger
+import mtmc.tokenizer.MTMCToken
+import mtmc.util.BinaryUtils
+
+class LoadStoreRegisterInstruction(
+ type: InstructionType,
+ label: List,
+ instructionToken: MTMCToken
+) : Instruction(type, label, instructionToken) {
+ private var targetToken: MTMCToken? = null
+ private var pointerToken: MTMCToken? = null
+ private var offsetToken: MTMCToken? = null
+
+ fun setTargetToken(targetToken: MTMCToken) {
+ this.targetToken = targetToken
+ }
+
+ fun setPointerToken(pointerToken: MTMCToken) {
+ this.pointerToken = pointerToken
+ }
+
+ fun setOffsetToken(offsetToken: MTMCToken?) {
+ this.offsetToken = offsetToken
+ }
+
+ override fun genCode(output: ByteArray, assembler: Assembler) {
+ var opcode = 0
+ when (type) {
+ InstructionType.LWR -> opcode = 4
+ InstructionType.LBR -> opcode = 5
+ InstructionType.SWR -> opcode = 6
+ InstructionType.SBR -> opcode = 7
+ else -> println("Invalid instruction type")
+ }
+ val target = toInteger(targetToken!!.stringValue)
+ val pointer = toInteger(pointerToken!!.stringValue)
+ var offset = Register.PC.ordinal
+ if (offsetToken != null) {
+ offset = toInteger(offsetToken!!.stringValue)
+ }
+ output[location] = (opcode shl 4 or target).toByte()
+ output[location + 1] = (pointer shl 4 or offset).toByte()
+ }
+
+ companion object {
+ fun disassemble(instruction: Short): String? {
+ if (BinaryUtils.getBits(16, 2, instruction).toInt() == 1) {
+ val builder = StringBuilder()
+ val type = BinaryUtils.getBits(14, 2, instruction)
+ if (type.toInt() == 0) {
+ builder.append("lwr ")
+ } else if (type.toInt() == 1) {
+ builder.append("lbr ")
+ } else if (type.toInt() == 2) {
+ builder.append("swr ")
+ } else if (type.toInt() == 3) {
+ builder.append("sbr ")
+ }
+ val srcDestReg = BinaryUtils.getBits(12, 4, instruction)
+ val addrReg = BinaryUtils.getBits(8, 4, instruction)
+ val offsetReg = BinaryUtils.getBits(4, 4, instruction)
+ builder.append(fromInteger(srcDestReg.toInt())).append(" ")
+ builder.append(fromInteger(addrReg.toInt())).append(" ")
+ builder.append(fromInteger(offsetReg.toInt()))
+ return builder.toString()
+ }
+ return null
+ }
+ }
+}
diff --git a/src/jvmMain/kotlin/mtmc/asm/instructions/MetaInstruction.kt b/src/jvmMain/kotlin/mtmc/asm/instructions/MetaInstruction.kt
new file mode 100644
index 0000000..4d45e25
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/asm/instructions/MetaInstruction.kt
@@ -0,0 +1,90 @@
+package mtmc.asm.instructions
+
+import mtmc.asm.Assembler
+import mtmc.tokenizer.MTMCToken
+
+class MetaInstruction(instruction: MTMCToken) :
+ Instruction(InstructionType.META, listOf(), instruction) {
+ private var originalFilePath: MTMCToken? = null
+ private var originaLineNumber: MTMCToken? = null
+ private var globalName: MTMCToken? = null
+ private var globalLocation: MTMCToken? = null
+ private var globalType: MTMCToken? = null
+ private var localName: MTMCToken? = null
+ private var localOffset: MTMCToken? = null
+ private var localType: MTMCToken? = null
+
+ override fun genCode(output: ByteArray, assembler: Assembler) {
+ // do nothing
+ }
+
+ val isFileDirective: Boolean
+ get() = "file" == this.instructionToken.stringValue
+
+ val isLineDirective: Boolean
+ get() = "line" == this.instructionToken.stringValue
+
+ val isGlobalDirective: Boolean
+ get() = "global" == this.instructionToken.stringValue
+
+ val isLocalDirective: Boolean
+ get() = "local" == this.instructionToken.stringValue
+
+ val isEndLocalDirective: Boolean
+ get() = "endlocal" == this.instructionToken.stringValue
+
+ fun setOriginalFilePath(path: MTMCToken) {
+ this.originalFilePath = path
+ }
+
+ fun setOriginalLineNumber(lineNumber: MTMCToken) {
+ this.originaLineNumber = lineNumber
+ }
+
+ val originalLineNumber: Int
+ get() = this.originaLineNumber!!.intValue()
+
+ fun setGlobalInfo(name: MTMCToken, location: MTMCToken, type: MTMCToken) {
+ this.globalName = name
+ this.globalLocation = location
+ this.globalType = type
+ }
+
+ fun setLocalInfo(name: MTMCToken, offset: MTMCToken, type: MTMCToken) {
+ this.localName = name
+ this.localOffset = offset
+ this.localType = type
+ }
+
+ fun setEndLocalInfo(name: MTMCToken) {
+ this.localName = name
+ }
+
+ fun getOriginalFilePath(): String? {
+ return this.originalFilePath!!.stringValue
+ }
+
+ fun getGlobalName(): String? {
+ return this.globalName!!.stringValue
+ }
+
+ fun getGlobalLocation(): Int {
+ return this.globalLocation!!.intValue()
+ }
+
+ fun getGlobalType(): String? {
+ return this.globalType!!.stringValue
+ }
+
+ fun getLocalName(): String? {
+ return this.localName!!.stringValue
+ }
+
+ fun getLocalOffset(): Int {
+ return this.localOffset!!.intValue()
+ }
+
+ fun getLocalType(): String? {
+ return this.localType!!.stringValue
+ }
+}
diff --git a/src/jvmMain/kotlin/mtmc/asm/instructions/MiscInstruction.kt b/src/jvmMain/kotlin/mtmc/asm/instructions/MiscInstruction.kt
new file mode 100644
index 0000000..994c61e
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/asm/instructions/MiscInstruction.kt
@@ -0,0 +1,145 @@
+package mtmc.asm.instructions
+
+import mtmc.asm.Assembler
+import mtmc.emulator.Register.Companion.fromInteger
+import mtmc.emulator.Register.Companion.toInteger
+import mtmc.os.SysCall
+import mtmc.tokenizer.MTMCToken
+import mtmc.util.BinaryUtils
+
+class MiscInstruction(
+ type: InstructionType,
+ labels: List,
+ instructionToken: MTMCToken
+) : Instruction(type, labels, instructionToken) {
+ private var syscallType: MTMCToken? = null
+ private var fromRegister: MTMCToken? = null
+ private var toRegister: MTMCToken? = null
+ private var value: MTMCToken? = null
+
+ fun setSyscallType(type: MTMCToken) {
+ this.syscallType = type
+ }
+
+ fun setFrom(fromRegister: MTMCToken) {
+ this.fromRegister = fromRegister
+ }
+
+ fun setTo(toRegister: MTMCToken) {
+ this.toRegister = toRegister
+ }
+
+ fun setValue(value: MTMCToken?) {
+ this.value = value
+ }
+
+ override fun genCode(output: ByteArray, assembler: Assembler) {
+ if (type == InstructionType.SYS) {
+ output[location] = 0
+ output[location + 1] = SysCall.getValue(this.syscallType!!.stringValue)
+ } else if (type == InstructionType.MOV) {
+ val to = toInteger(toRegister!!.stringValue)
+ val from = toInteger(fromRegister!!.stringValue)
+ output[location] = 1
+ output[location + 1] = (to shl 4 or from).toByte()
+ } else if (type == InstructionType.INC) {
+ output[location] = 2
+ val to = toInteger(toRegister!!.stringValue)
+ var immediateVal = 1
+ if (value != null) {
+ immediateVal = value!!.intValue()
+ }
+ output[location + 1] = (to shl 4 or immediateVal).toByte()
+ } else if (type == InstructionType.DEC) {
+ output[location] = 3
+ val to = toInteger(toRegister!!.stringValue)
+ var immediateVal = 1
+ if (value != null) {
+ immediateVal = value!!.intValue()
+ }
+ output[location + 1] = (to shl 4 or immediateVal).toByte()
+ } else if (type == InstructionType.SETI) {
+ output[location] = 4
+ val to = toInteger(toRegister!!.stringValue)
+ val immediateVal = value!!.intValue()
+ output[location + 1] = (to shl 4 or immediateVal).toByte()
+ } else if (type == InstructionType.MCP) {
+ output[location] = 5
+ val from = toInteger(fromRegister!!.stringValue)
+ val to = toInteger(toRegister!!.stringValue)
+ val value = this.value!!.intValue()
+ output[location + 1] = (from shl 4 or to).toByte()
+ output[location + 2] = (value shl 8).toByte()
+ output[location + 3] = (value and 0xFF).toByte()
+ } else if (type == InstructionType.DEBUG) {
+ output[location] = 8
+ output[location + 1] = value!!.intValue().toByte()
+ } else if (type == InstructionType.NOP) {
+ output[location] = 15
+ output[location + 1] = 255.toByte()
+ }
+ }
+
+ companion object {
+ fun disassemble(instruction: Short): String? {
+ if (BinaryUtils.getBits(16, 4, instruction).toInt() == 0) {
+ val topNibble = BinaryUtils.getBits(12, 4, instruction)
+ if (topNibble.toInt() == 0) {
+ val builder = StringBuilder("sys ")
+ val bits = BinaryUtils.getBits(8, 8, instruction)
+ val name = SysCall.getString(bits.toByte())
+ builder.append(name)
+ return builder.toString()
+ } else if (topNibble.toInt() == 1) {
+ val builder = StringBuilder("mov ")
+ val to = BinaryUtils.getBits(8, 4, instruction)
+ val from = BinaryUtils.getBits(4, 4, instruction)
+ val toName = fromInteger(to.toInt())
+ builder.append(toName).append(" ")
+ val fromName = fromInteger(from.toInt())
+ builder.append(fromName)
+ return builder.toString()
+ } else if (topNibble.toInt() == 2) {
+ val builder = StringBuilder("inc ")
+ val to = BinaryUtils.getBits(8, 4, instruction)
+ val amount = BinaryUtils.getBits(4, 4, instruction)
+ val toName = fromInteger(to.toInt())
+ builder.append(toName).append(" ")
+ builder.append(amount.toInt())
+ return builder.toString()
+ } else if (topNibble.toInt() == 3) {
+ val builder = StringBuilder("dec ")
+ val to = BinaryUtils.getBits(8, 4, instruction)
+ val amount = BinaryUtils.getBits(4, 4, instruction)
+ val toName = fromInteger(to.toInt())
+ builder.append(toName).append(" ")
+ builder.append(amount.toInt())
+ return builder.toString()
+ } else if (topNibble.toInt() == 4) {
+ val builder = StringBuilder("seti ")
+ val to = BinaryUtils.getBits(8, 4, instruction)
+ val amount = BinaryUtils.getBits(4, 4, instruction)
+ val toName = fromInteger(to.toInt())
+ builder.append(toName).append(" ")
+ builder.append(amount.toInt())
+ return builder.toString()
+ } else if (topNibble.toInt() == 5) {
+ val from = BinaryUtils.getBits(8, 4, instruction)
+ val fromName = fromInteger(from.toInt())
+ val to = BinaryUtils.getBits(4, 4, instruction)
+ val toName = fromInteger(to.toInt())
+ return "mcp " + fromName + " " + toName
+ } else if (topNibble.toInt() == 8) {
+ val builder = StringBuilder("debug ")
+ val stringIndex = BinaryUtils.getBits(8, 8, instruction)
+ builder.append(stringIndex.toInt())
+ return builder.toString()
+ } else if (topNibble.toInt() == 15) {
+ val builder = StringBuilder("noop")
+ return builder.toString()
+ }
+ }
+ return null
+ }
+ }
+}
diff --git a/src/jvmMain/kotlin/mtmc/asm/instructions/StackInstruction.kt b/src/jvmMain/kotlin/mtmc/asm/instructions/StackInstruction.kt
new file mode 100644
index 0000000..240bd23
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/asm/instructions/StackInstruction.kt
@@ -0,0 +1,108 @@
+package mtmc.asm.instructions
+
+import mtmc.asm.Assembler
+import mtmc.asm.instructions.ALUOp.Companion.fromInt
+import mtmc.emulator.Register
+import mtmc.emulator.Register.Companion.fromInteger
+import mtmc.tokenizer.MTMCToken
+import mtmc.util.BinaryUtils
+
+class StackInstruction(
+ type: InstructionType,
+ label: List,
+ instructionToken: MTMCToken
+) : Instruction(type, label, instructionToken) {
+ private var targetToken: MTMCToken? = null
+ private var stackRegisterToken: MTMCToken? = null
+ private var aluOpToken: MTMCToken? = null
+ private var value: MTMCToken? = null
+
+ fun setTarget(target: MTMCToken) {
+ this.targetToken = target
+ }
+
+ fun setStackRegister(stackRegister: MTMCToken?) {
+ this.stackRegisterToken = stackRegister
+ }
+
+ fun setALUOp(aluOp: MTMCToken) {
+ this.aluOpToken = aluOp
+ }
+
+ fun setValue(value: MTMCToken) {
+ this.value = value
+ }
+
+ override fun genCode(output: ByteArray, assembler: Assembler) {
+ var stackReg = Register.SP.ordinal
+ if (stackRegisterToken != null) {
+ stackReg = Register.Companion.toInteger(stackRegisterToken!!.stringValue)
+ }
+ if (type == InstructionType.PUSH) {
+ val target = Register.Companion.toInteger(targetToken!!.stringValue)
+ output[location] = 32
+ output[location + 1] = (target shl 4 or stackReg).toByte()
+ } else if (type == InstructionType.POP) {
+ val target = Register.Companion.toInteger(targetToken!!.stringValue)
+ output[location] = 33
+ output[location + 1] = (target shl 4 or stackReg).toByte()
+ } else if (type == InstructionType.SOP) {
+ val aluOp = ALUOp.toInteger(aluOpToken!!.stringValue)
+ output[location] = 39
+ output[location + 1] = (aluOp shl 4 or stackReg).toByte()
+ } else if (type == InstructionType.PUSHI) {
+ val immediateValue = value!!.intValue()
+ output[location] = 47
+ output[location + 1] = stackReg.toByte()
+ output[location + 2] = (immediateValue ushr 8).toByte()
+ output[location + 3] = immediateValue.toByte()
+ } else {
+ val stackOp: Int
+ stackOp = when (type) {
+ InstructionType.DUP -> 34
+ InstructionType.SWAP -> 35
+ InstructionType.DROP -> 36
+ InstructionType.OVER -> 37
+ InstructionType.ROT -> 38
+ else -> 0
+ }
+ output[location] = stackOp.toByte()
+ output[location + 1] = stackReg.toByte()
+ }
+ }
+
+ companion object {
+ fun disassemble(instruction: Short): String? {
+ if (BinaryUtils.getBits(16, 4, instruction).toInt() == 2) {
+ val opCode = BinaryUtils.getBits(12, 4, instruction)
+ val stackReg = BinaryUtils.getBits(4, 4, instruction)
+ if (opCode.toInt() == 0) {
+ val sourceReg = BinaryUtils.getBits(8, 4, instruction)
+ return "push " + fromInteger(sourceReg.toInt()) + " " + fromInteger(stackReg.toInt())
+ }
+ if (opCode.toInt() == 1) {
+ val destReg = BinaryUtils.getBits(8, 4, instruction)
+ return "pop " + fromInteger(destReg.toInt()) + " " + fromInteger(stackReg.toInt())
+ }
+ if (opCode.toInt() == 2) {
+ return "dup " + fromInteger(stackReg.toInt())
+ } else if (opCode.toInt() == 3) {
+ return "swap " + fromInteger(stackReg.toInt())
+ } else if (opCode.toInt() == 4) {
+ return "drop " + fromInteger(stackReg.toInt())
+ } else if (opCode.toInt() == 5) {
+ return "over " + fromInteger(stackReg.toInt())
+ } else if (opCode.toInt() == 6) {
+ return "rot " + fromInteger(stackReg.toInt())
+ } else if (opCode.toInt() == 7) {
+ val aluOp = BinaryUtils.getBits(8, 4, instruction)
+ val op = fromInt(aluOp)
+ return "sop " + op + " " + fromInteger(stackReg.toInt())
+ } else if (opCode.toInt() == 15) {
+ return "pushi " + fromInteger(stackReg.toInt())
+ }
+ }
+ return null
+ }
+ }
+}
diff --git a/src/jvmMain/kotlin/mtmc/asm/instructions/TestInstruction.kt b/src/jvmMain/kotlin/mtmc/asm/instructions/TestInstruction.kt
new file mode 100644
index 0000000..235c8b3
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/asm/instructions/TestInstruction.kt
@@ -0,0 +1,126 @@
+package mtmc.asm.instructions
+
+import mtmc.asm.Assembler
+import mtmc.emulator.Register.Companion.fromInteger
+import mtmc.emulator.Register.Companion.toInteger
+import mtmc.tokenizer.MTMCToken
+import mtmc.util.BinaryUtils
+
+class TestInstruction(
+ type: InstructionType,
+ label: List,
+ instructionToken: MTMCToken
+) : Instruction(type, label, instructionToken) {
+ private var first: MTMCToken? = null
+ private var second: MTMCToken? = null
+ private var value: MTMCToken? = null
+
+ fun setFirst(to: MTMCToken) {
+ this.first = to
+ }
+
+ fun setSecond(from: MTMCToken) {
+ this.second = from
+ }
+
+ fun setImmediateValue(value: MTMCToken) {
+ this.value = value
+ }
+
+ val isImmediate: Boolean
+ get() = type.name.endsWith("I")
+
+ override fun genCode(output: ByteArray, assembler: Assembler) {
+ val firstReg = toInteger(first!!.stringValue)
+ if (this.isImmediate) {
+ val upperByte = when (type) {
+ InstructionType.EQI -> 56
+ InstructionType.NEQI -> 57
+ InstructionType.GTI -> 58
+ InstructionType.GTEI -> 59
+ InstructionType.LTI -> 60
+ InstructionType.LTEI -> 61
+ else -> 0
+ }
+ val immediateValue = value!!.intValue()
+ output[location] = upperByte.toByte()
+ output[location + 1] = (firstReg shl 4 or immediateValue).toByte()
+ } else {
+ val upperByte = when (type) {
+ InstructionType.EQ -> 48
+ InstructionType.NEQ -> 49
+ InstructionType.GT -> 50
+ InstructionType.GTE -> 51
+ InstructionType.LT -> 52
+ InstructionType.LTE -> 53
+ else -> 0
+ }
+ val secondReg = toInteger(second!!.stringValue)
+ output[location] = upperByte.toByte()
+ output[location + 1] = (firstReg shl 4 or secondReg).toByte()
+ }
+ }
+
+ companion object {
+ fun disassemble(instruction: Short): String? {
+ if (BinaryUtils.getBits(16, 4, instruction).toInt() == 3) {
+ val sb = StringBuilder()
+ val opCode = BinaryUtils.getBits(12, 4, instruction)
+ val thirdNibble = BinaryUtils.getBits(8, 4, instruction)
+ val fourthNibble = BinaryUtils.getBits(4, 4, instruction)
+ if (opCode.toInt() == 0) {
+ sb.append("eq ")
+ sb.append(fromInteger(thirdNibble.toInt()))
+ .append(" ").append(fromInteger(fourthNibble.toInt()))
+ } else if (opCode.toInt() == 1) {
+ sb.append("neq ")
+ sb.append(fromInteger(thirdNibble.toInt()))
+ .append(" ").append(fromInteger(fourthNibble.toInt()))
+ } else if (opCode.toInt() == 2) {
+ sb.append("gt ")
+ sb.append(fromInteger(thirdNibble.toInt()))
+ .append(" ").append(fromInteger(fourthNibble.toInt()))
+ } else if (opCode.toInt() == 3) {
+ sb.append("gte ")
+ sb.append(fromInteger(thirdNibble.toInt()))
+ .append(" ").append(fromInteger(fourthNibble.toInt()))
+ } else if (opCode.toInt() == 4) {
+ sb.append("lt ")
+ sb.append(fromInteger(thirdNibble.toInt()))
+ .append(" ").append(fromInteger(fourthNibble.toInt()))
+ } else if (opCode.toInt() == 5) {
+ sb.append("lte ")
+ sb.append(fromInteger(thirdNibble.toInt()))
+ .append(" ").append(fromInteger(fourthNibble.toInt()))
+ }
+ if (opCode.toInt() == 8) {
+ sb.append("eqi ")
+ sb.append(fromInteger(thirdNibble.toInt()))
+ .append(" ").append(fourthNibble.toInt())
+ } else if (opCode.toInt() == 9) {
+ sb.append("neqi ")
+ sb.append(fromInteger(thirdNibble.toInt()))
+ .append(" ").append(fourthNibble.toInt())
+ } else if (opCode.toInt() == 10) {
+ sb.append("gti ")
+ sb.append(fromInteger(thirdNibble.toInt()))
+ .append(" ").append(fourthNibble.toInt())
+ } else if (opCode.toInt() == 11) {
+ sb.append("gtei ")
+ sb.append(fromInteger(thirdNibble.toInt()))
+ .append(" ").append(fourthNibble.toInt())
+ } else if (opCode.toInt() == 12) {
+ sb.append("lti ")
+ sb.append(fromInteger(thirdNibble.toInt()))
+ .append(" ").append(fourthNibble.toInt())
+ } else if (opCode.toInt() == 13) {
+ sb.append("ltei ")
+ sb.append(fromInteger(thirdNibble.toInt()))
+ .append(" ").append(fourthNibble.toInt())
+ }
+ return sb.toString()
+ }
+ return null
+ }
+ }
+}
diff --git a/src/jvmMain/kotlin/nl/astraeus/tmpl/db/Database.kt b/src/jvmMain/kotlin/mtmc/db/Database.kt
similarity index 98%
rename from src/jvmMain/kotlin/nl/astraeus/tmpl/db/Database.kt
rename to src/jvmMain/kotlin/mtmc/db/Database.kt
index 9df850a..d29586c 100644
--- a/src/jvmMain/kotlin/nl/astraeus/tmpl/db/Database.kt
+++ b/src/jvmMain/kotlin/mtmc/db/Database.kt
@@ -1,4 +1,4 @@
-package nl.astraeus.tmpl.db
+package mtmc.db
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
diff --git a/src/jvmMain/kotlin/nl/astraeus/tmpl/db/Migrations.kt b/src/jvmMain/kotlin/mtmc/db/Migrations.kt
similarity index 97%
rename from src/jvmMain/kotlin/nl/astraeus/tmpl/db/Migrations.kt
rename to src/jvmMain/kotlin/mtmc/db/Migrations.kt
index 60d075b..7a70d22 100644
--- a/src/jvmMain/kotlin/nl/astraeus/tmpl/db/Migrations.kt
+++ b/src/jvmMain/kotlin/mtmc/db/Migrations.kt
@@ -1,6 +1,6 @@
-package nl.astraeus.tmpl.db
+package mtmc.db
-import nl.astraeus.tmpl.log
+import mtmc.log
import java.sql.Connection
import java.sql.SQLException
import java.sql.Timestamp
diff --git a/src/jvmMain/kotlin/mtmc/emulator/DebugInfo.kt b/src/jvmMain/kotlin/mtmc/emulator/DebugInfo.kt
new file mode 100644
index 0000000..e40ec01
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/emulator/DebugInfo.kt
@@ -0,0 +1,45 @@
+package mtmc.emulator
+
+import java.util.*
+import java.util.regex.Pattern
+
+@JvmRecord
+data class DebugInfo(
+ val debugStrings: MutableList,
+ val assemblyFile: String?,
+ val assemblySource: String,
+ val assemblyLineNumbers: IntArray,
+ val originalFile: String,
+ val originalLineNumbers: IntArray,
+ val globals: Array,
+ val locals: Array>
+) {
+ fun handleDebugString(debugIndex: Short, monTanaMiniComputer: MonTanaMiniComputer) {
+ val debugString = debugStrings!!.get(debugIndex.toInt())
+ val compile = Pattern.compile("(\\$[a-zA-Z][a-zA-Z0-9])")
+ val matcher = compile.matcher(debugString)
+ val formattedString = StringBuilder()
+ var start = 0
+ var end: Int
+ while (matcher.find()) {
+ val match = matcher.group().substring(1)
+ try {
+ end = matcher.start()
+ formattedString.append(debugString, start, end)
+ val register = Register.valueOf(match.uppercase(Locale.getDefault()))
+ formattedString.append(monTanaMiniComputer.getRegisterValue(register).toInt())
+ start = matcher.end()
+ } catch (e: Exception) {
+ formattedString.append(match)
+ }
+ }
+ formattedString.append(debugString.substring(start))
+ println("DEBUG[" + System.nanoTime() + "] : " + formattedString)
+ }
+
+ @JvmRecord
+ data class GlobalInfo(val name: String?, val location: Int, val type: String?)
+
+ @JvmRecord
+ data class LocalInfo(val name: String?, val offset: Int, val type: String?)
+}
\ No newline at end of file
diff --git a/src/jvmMain/kotlin/mtmc/emulator/MTMCClock.kt b/src/jvmMain/kotlin/mtmc/emulator/MTMCClock.kt
new file mode 100644
index 0000000..d440874
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/emulator/MTMCClock.kt
@@ -0,0 +1,71 @@
+package mtmc.emulator
+
+import mtmc.emulator.MonTanaMiniComputer.ComputerStatus
+import kotlin.math.max
+
+/**
+ *
+ * @author jbanes
+ */
+class MTMCClock
+ (private val computer: MonTanaMiniComputer) {
+ fun run() {
+ var instructions: Long = 0
+ var ips: Long = 0
+ var expected: Long = 0
+ var virtual: Long = 0
+
+ var startTime = System.currentTimeMillis()
+ var deltaStart: Long
+ var delta: Long
+
+ var speed: Long = 0
+ var pulse: Long
+ var ms: Long = 10
+
+ while (computer.getStatus() == ComputerStatus.EXECUTING) {
+ speed = max(computer.speed, 0).toLong()
+ pulse = (if (speed <= 0) 1000000 else max(speed / 100, 1))
+ ms = (if (pulse < 10) 1000 / speed else 10)
+
+ deltaStart = System.currentTimeMillis()
+ delta = ms - (System.currentTimeMillis() - deltaStart)
+
+
+ /* We've lost more than a second. Recalibrate. */
+ if ((expected - virtual) > pulse * 100) {
+ startTime = deltaStart
+ virtual = 0
+ }
+
+
+ /* Throttles to every 10ms, but "catches up" if we're behind */
+ if (delta > 0 && (expected - virtual) < pulse && speed != 0L) {
+ try {
+ Thread.sleep(delta)
+ } catch (e: InterruptedException) {
+ }
+ }
+
+ instructions += computer.pulse(pulse)
+
+ virtual += pulse
+ ips = (virtual * 1000) / max(1, System.currentTimeMillis() - startTime)
+ expected = (System.currentTimeMillis() - startTime) * speed / 1000
+ }
+
+ System.err.println("Executed " + instructions + " instructions at a rate of " + ips + " ips (speed = " + speed + ")")
+ }
+
+ fun step() {
+ computer.fetchAndExecute()
+ computer.fetchCurrentInstruction()
+ computer.notifyOfStepExecution()
+ }
+
+ fun back() {
+ computer.rewind()
+ computer.fetchCurrentInstruction()
+ computer.notifyOfStepExecution()
+ }
+}
diff --git a/src/jvmMain/kotlin/mtmc/emulator/MTMCConsole.kt b/src/jvmMain/kotlin/mtmc/emulator/MTMCConsole.kt
new file mode 100644
index 0000000..75fb03f
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/emulator/MTMCConsole.kt
@@ -0,0 +1,131 @@
+package mtmc.emulator
+
+import mtmc.os.shell.Shell
+import mtmc.tokenizer.MTMCScanner
+import mtmc.tokenizer.MTMCToken
+import java.io.Console
+
+class MTMCConsole(private val computer: MonTanaMiniComputer) {
+ var mode: Mode = Mode.NON_INTERACTIVE
+ var sysConsole: Console? = null
+
+ // non-interactive data
+ private val output = StringBuffer()
+ private var shortValueSet = false
+ private var shortValue: Short = 0
+ private var stringValue: String? = null
+
+ // TODO invert so shell is driving and console is just IO
+ fun start() {
+ mode = Mode.INTERACTIVE
+ sysConsole = System.console()
+ Shell.printShellWelcome(computer)
+ while (true) {
+ val cmd = sysConsole!!.readLine("mtmc > ")
+ computer.oS.processCommand(cmd)
+ }
+ }
+
+ fun println(x: String) {
+ print(x)
+ print("\n")
+ }
+
+ fun print(x: String) {
+ output.append(x)
+ if (mode == Mode.INTERACTIVE) {
+ kotlin.io.print(x)
+ } else {
+ if (x.contains("\n")) {
+ computer.notifyOfConsoleUpdate()
+ } else {
+ computer.notifyOfConsolePrinting()
+ }
+ }
+ }
+
+ fun readChar(): Char {
+ if (mode == Mode.INTERACTIVE) {
+ val tokens = MTMCScanner(sysConsole!!.readLine(), "#").tokenize()
+ val token = tokens.first()
+ assert(token.type === MTMCToken.TokenType.CHAR)
+ return token.charValue()
+ } else {
+ this.shortValueSet = false
+ return Char(this.shortValue.toUShort())
+ }
+ }
+
+ fun readInt(): Short {
+ if (mode == Mode.INTERACTIVE) {
+ return sysConsole!!.readLine().toShort()
+ } else {
+ this.shortValueSet = false
+ return shortValue
+ }
+ }
+
+ fun hasShortValue(): Boolean {
+ return (mode == Mode.INTERACTIVE || shortValueSet)
+ }
+
+ fun setShortValue(shortValue: Short) {
+ this.shortValue = shortValue
+ this.shortValueSet = true
+ }
+
+ fun setCharValue(charValue: Char) {
+ this.shortValue = charValue.code.toShort()
+ this.shortValueSet = true
+ }
+
+ fun getOutput(): String {
+ return output.toString()
+ }
+
+ fun consumeLines(): String {
+ val index = output.lastIndexOf("\n")
+ val text = if (index >= 0) output.substring(0, index + 1) else ""
+
+ if (index >= 0) {
+ output.delete(0, index + 1)
+ }
+
+ return text
+ }
+
+ fun writeInt(value: Short) {
+ print(value.toString() + "")
+ }
+
+ fun setStringValue(stringValue: String?) {
+ this.stringValue = stringValue
+ }
+
+ fun readString(): String? {
+ if (mode == Mode.INTERACTIVE) {
+ return sysConsole!!.readLine()
+ } else {
+ val stringValue = this.stringValue
+ this.stringValue = null
+ return stringValue
+ }
+ }
+
+ fun hasReadString(): Boolean {
+ return (mode == Mode.INTERACTIVE || stringValue != null)
+ }
+
+ fun setReadString(stringValue: String?) {
+ this.stringValue = stringValue
+ }
+
+ fun resetOutput() {
+ output.delete(0, output.length)
+ }
+
+ enum class Mode {
+ NON_INTERACTIVE,
+ INTERACTIVE,
+ }
+}
diff --git a/src/jvmMain/kotlin/mtmc/emulator/MTMCDisplay.kt b/src/jvmMain/kotlin/mtmc/emulator/MTMCDisplay.kt
new file mode 100644
index 0000000..70d984d
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/emulator/MTMCDisplay.kt
@@ -0,0 +1,284 @@
+package mtmc.emulator
+
+import java.awt.Color
+import java.awt.Dimension
+import java.awt.RenderingHints
+import java.awt.image.BufferedImage
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.IOException
+import java.util.*
+import javax.imageio.ImageIO
+import kotlin.math.min
+
+class MTMCDisplay(private val computer: MonTanaMiniComputer) {
+ private val buffer = BufferedImage(
+ COLS,
+ ROWS,
+ BufferedImage.TYPE_INT_ARGB
+ )
+ private var currentColor: DisplayColor? = null
+ private var graphics: Array = arrayOf()
+ private var byteArray: ByteArray = ByteArray(0)
+
+ fun setColor(registerValue: Short) {
+ currentColor = DisplayColor.entries[min(registerValue.toInt(), 3)]
+ }
+
+ enum class DisplayColor(val r: Int, val g: Int, val b: Int) {
+ DARK(42, 69, 59),
+ MEDIUM(54, 93, 72),
+ LIGHT(87, 124, 68),
+ LIGHTEST(127, 134, 15);
+
+ val intVal: Int
+ val javaColor: Color
+
+ init {
+ this.intVal = 0xFF shl 24 or (r shl 16) or (g shl 8) or b
+ javaColor = Color(r, g, b)
+ }
+
+ fun distance(r: Int, g: Int, b: Int): Int {
+ val dr = this.r - r
+ val dg = this.g - g
+ val db = this.b - b
+ val square = dr * dr + dg * dg + db * db
+ return square
+ }
+
+ companion object {
+ fun indexFromInt(value: Int): Short {
+ if (value == LIGHTEST.intVal) {
+ return 3
+ } else if (value == LIGHT.intVal) {
+ return 2
+ } else if (value == MEDIUM.intVal) {
+ return 1
+ } else {
+ return 0
+ }
+ }
+ }
+ }
+
+ init {
+ reset()
+ loadSplashScreen()
+ sync()
+ }
+
+ private fun loadSplashScreen() {
+ try {
+ val bytes: ByteArray = Base64.getDecoder().decode(SPLASH_SCREEN)
+ val bais = ByteArrayInputStream(bytes)
+ var img: BufferedImage? = null
+ img = ImageIO.read(bais)
+ loadScaledImage(img)
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+ }
+
+ private fun loadImage(data: ByteArray): BufferedImage? {
+ try {
+ return ImageIO.read(ByteArrayInputStream(data))
+ } catch (e: IOException) {
+ e.printStackTrace()
+ throw IllegalStateException(e)
+ }
+ }
+
+ fun loadGraphics(data: Array) {
+ if (data == null) {
+ graphics = arrayOf()
+ return
+ }
+
+ graphics = Array(data.size) { index ->
+ loadImage(data[index]) ?: BufferedImage(160, 144, BufferedImage.TYPE_INT_RGB)
+ }
+ }
+
+ fun reset() {
+ currentColor = DisplayColor.DARK
+ for (col in 0..= 0 && index < graphics!!.size)
+ }
+
+ fun setPixel(col: Int, row: Int, value: Int) {
+ val color = DisplayColor.entries[value]
+ setPixel(col, row, color)
+ }
+
+ fun setPixel(col: Int, row: Int, color: DisplayColor) {
+ buffer.setRGB(col, row, color.intVal)
+ }
+
+ fun getPixel(col: Int, row: Int): Short {
+ val rgb = buffer.getRGB(col, row)
+ return DisplayColor.Companion.indexFromInt(rgb)
+ }
+
+ fun drawLine(startCol: Short, startRow: Short, endCol: Short, endRow: Short) {
+ val graphics = buffer.getGraphics()
+ graphics.setColor(currentColor!!.javaColor)
+ graphics.drawLine(startCol.toInt(), startRow.toInt(), endCol.toInt(), endRow.toInt())
+ graphics.dispose()
+ }
+
+ fun drawRectangle(startCol: Short, startRow: Short, width: Short, height: Short) {
+ val graphics = buffer.getGraphics()
+ graphics.setColor(currentColor!!.javaColor)
+ graphics.fillRect(startCol.toInt(), startRow.toInt(), width.toInt(), height.toInt())
+ graphics.dispose()
+ }
+
+ fun drawImage(image: Int, x: Int, y: Int) {
+ val graphic = graphics!![image]
+ val graphics = buffer.getGraphics()
+ graphics.drawImage(graphic, x, y, null)
+ graphics.dispose()
+ }
+
+ fun drawImage(image: Int, x: Int, y: Int, width: Int, height: Int) {
+ val graphic = graphics!![image]
+ val graphics = buffer.getGraphics()
+ graphics.drawImage(graphic, x, y, width, height, null)
+ graphics.dispose()
+ }
+
+ fun drawImage(
+ image: Int,
+ sx: Int,
+ sy: Int,
+ sw: Int,
+ sh: Int,
+ dx: Int,
+ dy: Int,
+ dw: Int,
+ dh: Int
+ ) {
+ val graphic = graphics!![image]
+ val graphics = buffer.getGraphics()
+ graphics.drawImage(graphic, dx, dy, dx + dw, dy + dh, sx, sy, sx + sw, sy + sh, null)
+ graphics.dispose()
+ }
+
+ fun sync() {
+ val baos = ByteArrayOutputStream()
+ try {
+ buffer.flush()
+ ImageIO.write(buffer, "png", baos)
+ byteArray = baos.toByteArray()
+ } catch (e: IOException) {
+ throw RuntimeException(e)
+ }
+ computer.notifyOfDisplayUpdate()
+ }
+
+ fun toPng(): ByteArray? {
+ return byteArray
+ }
+
+ //=============================================
+ // utilities
+ //=============================================
+ fun loadScaledImage(img: BufferedImage) {
+ val scaleDimensions: Dimension = getScaledDimension(img, COLS, ROWS)
+ val scaledImage: BufferedImage = scaleImage(img, scaleDimensions)
+ val xpad: Int = (COLS - scaledImage.getWidth()) / 2
+ val ypad: Int = (ROWS - scaledImage.getHeight()) / 2
+ for (x in 0.. 0xFF / 2) {
+ val displayColor: DisplayColor = findClosestColor(rgb)
+ setPixel((x + xpad).toShort().toInt(), (y + ypad).toShort().toInt(), displayColor)
+ }
+ }
+ }
+ }
+
+ companion object {
+ const val ROWS: Int = 144
+ const val COLS: Int = 160
+ const val SPLASH_SCREEN: String =
+ "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"
+
+ fun getScaledDimension(image: BufferedImage, widthBound: Int, heightBound: Int): Dimension {
+ val originalWidth = image.getWidth()
+ val originalHeight = image.getHeight()
+ var newWidth = originalWidth
+ var newHeight = originalHeight
+
+ // first check if we need to scale width
+ if (originalWidth > widthBound) {
+ //scale width to fit
+ newWidth = widthBound
+ //scale height to maintain aspect ratio
+ newHeight = (newWidth * originalHeight) / originalWidth
+ }
+
+ // then check if we need to scale even with the new height
+ if (newHeight > heightBound) {
+ //scale height to fit instead
+ newHeight = heightBound
+ //scale width to maintain aspect ratio
+ newWidth = (newHeight * originalWidth) / originalHeight
+ }
+ return Dimension(newWidth, newHeight)
+ }
+
+ fun scaleImage(original: BufferedImage, scaleDimensions: Dimension): BufferedImage {
+ val resized = BufferedImage(scaleDimensions.width, scaleDimensions.height, original.getType())
+ val g = resized.createGraphics()
+ g.setRenderingHint(
+ RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_BILINEAR
+ )
+ g.drawImage(
+ original, 0, 0, scaleDimensions.width, scaleDimensions.height, 0, 0, original.getWidth(),
+ original.getHeight(), null
+ )
+ g.dispose()
+ return resized
+ }
+
+ fun convertImage(image: BufferedImage): BufferedImage {
+ val arbg = BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_4BYTE_ABGR)
+ for (x in 0.. 0xFF / 2) {
+ val displayColor: DisplayColor = findClosestColor(rgb)
+ arbg.setRGB(x, y, displayColor.intVal)
+ }
+ }
+ }
+ return arbg
+ }
+
+ private fun findClosestColor(colorVal: Int): DisplayColor {
+ val r = colorVal shr 16 and 255
+ val g = colorVal shr 8 and 255
+ val b = colorVal and 255
+ var closest = DisplayColor.DARK
+ for (color in DisplayColor.entries) {
+ if (color.distance(r, g, b) < closest.distance(r, g, b)) {
+ closest = color
+ }
+ }
+ return closest
+ }
+ }
+}
diff --git a/src/jvmMain/kotlin/mtmc/emulator/MTMCIO.kt b/src/jvmMain/kotlin/mtmc/emulator/MTMCIO.kt
new file mode 100644
index 0000000..5cbec59
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/emulator/MTMCIO.kt
@@ -0,0 +1,28 @@
+package mtmc.emulator
+
+import java.util.*
+
+class MTMCIO {
+ var value: Int = 0
+
+ internal enum class Buttons(val mask: Int) {
+ UP(128),
+ DOWN(64),
+ LEFT(32),
+ RIGHT(16),
+ START(8),
+ SELECT(4),
+ B(2),
+ A(1)
+ }
+
+ fun keyPressed(key: String) {
+ val button = Buttons.valueOf(key.uppercase(Locale.getDefault()))
+ value = value or button.mask
+ }
+
+ fun keyReleased(key: String) {
+ val button = Buttons.valueOf(key.uppercase(Locale.getDefault()))
+ value = value and button.mask.inv()
+ }
+}
diff --git a/src/jvmMain/kotlin/mtmc/emulator/MTMCObserver.kt b/src/jvmMain/kotlin/mtmc/emulator/MTMCObserver.kt
new file mode 100644
index 0000000..d3fb450
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/emulator/MTMCObserver.kt
@@ -0,0 +1,33 @@
+package mtmc.emulator
+
+interface MTMCObserver {
+ fun consoleUpdated()
+
+ fun consolePrinting()
+
+ fun executionUpdated()
+
+ fun filesystemUpdated()
+
+ fun registerUpdated(register: Int, value: Int)
+
+ fun memoryUpdated(address: Int, value: Byte)
+
+ fun displayUpdated()
+
+ fun instructionFetched(instruction: Short)
+
+ fun beforeExecution(instruction: Short)
+
+ fun afterExecution(instruction: Short)
+
+ fun stepExecution()
+
+ fun computerReset()
+
+ fun requestCharacter()
+
+ fun requestInteger()
+
+ fun requestString()
+}
diff --git a/src/jvmMain/kotlin/mtmc/emulator/MonTanaMiniComputer.kt b/src/jvmMain/kotlin/mtmc/emulator/MonTanaMiniComputer.kt
new file mode 100644
index 0000000..015d4df
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/emulator/MonTanaMiniComputer.kt
@@ -0,0 +1,1030 @@
+package mtmc.emulator
+
+import mtmc.asm.instructions.Instruction
+import mtmc.os.MTOS
+import mtmc.os.fs.FileSystem
+import mtmc.util.BinaryUtils.getBits
+import java.lang.Byte
+import java.util.*
+import java.util.function.Consumer
+import java.util.stream.IntStream
+import kotlin.Array
+import kotlin.Boolean
+import kotlin.ByteArray
+import kotlin.Int
+import kotlin.IntArray
+import kotlin.Long
+import kotlin.Short
+import kotlin.ShortArray
+import kotlin.String
+import kotlin.experimental.and
+import kotlin.experimental.inv
+import kotlin.experimental.or
+import kotlin.math.max
+import kotlin.math.min
+
+class MonTanaMiniComputer {
+ // core model
+ var registerFile: ShortArray =
+ ShortArray(Register.entries.size)// 16 user visible + the instruction register
+ var memory: ByteArray = ByteArray(MEMORY_SIZE)
+ var breakpoints: ByteArray = ByteArray(MEMORY_SIZE)
+ private var status: ComputerStatus? = ComputerStatus.READY
+ var speed: Int = 1000000
+ set(speed) {
+ field = speed
+ this.notifyOfExecutionUpdate()
+ }
+ val iO: MTMCIO = MTMCIO()
+
+ // helpers
+ var os: MTOS = MTOS(this)
+ var console: MTMCConsole = MTMCConsole(this)
+ var display: MTMCDisplay = MTMCDisplay(this)
+ var clock: MTMCClock = MTMCClock(this)
+ var fileSystem: FileSystem = FileSystem(this)
+ var rewindSteps: LinkedList? = LinkedList()
+
+ // listeners
+ private val observers: MutableList = ArrayList()
+ var debugInfo: DebugInfo? = null
+ private var currentRewindStep: RewindStep? = null
+
+ init {
+ initMemory()
+ }
+
+ fun initMemory() {
+ registerFile = ShortArray(Register.entries.size)
+ memory = ByteArray(MEMORY_SIZE)
+ breakpoints = ByteArray(MEMORY_SIZE)
+ rewindSteps = null
+ setRegisterValue(
+ Register.SP,
+ MEMORY_SIZE.toShort().toInt()
+ ) // default the stack pointer to the top of memory
+ rewindSteps = LinkedList()
+ observers!!.forEach(Consumer { obj: MTMCObserver? -> obj!!.computerReset() })
+ }
+
+ fun load(code: ByteArray, data: ByteArray, debugInfo: DebugInfo?) {
+ load(code, data, Array(0) { ByteArray(0) }, debugInfo)
+ }
+
+ fun load(code: ByteArray, data: ByteArray, graphics: Array, debugInfo: DebugInfo?) {
+ this.debugInfo = debugInfo
+
+ // reset memory
+ initMemory()
+
+
+ val codeBoundary = code.size
+ System.arraycopy(code, 0, memory, 0, codeBoundary)
+ setRegisterValue(Register.CB, codeBoundary - 1)
+
+ val dataBoundary = codeBoundary + data.size
+ System.arraycopy(data, 0, memory, codeBoundary, data.size)
+ setRegisterValue(Register.DB, dataBoundary - 1)
+
+
+ // base pointer starts just past the end of the data boundary
+ setRegisterValue(Register.BP, dataBoundary)
+
+ fetchCurrentInstruction() // fetch the initial instruction for display purposes
+ notifyOfStepExecution() // prepare for step execution
+ display.loadGraphics(graphics) // ready graphics for use
+
+ // reset computer status
+ setStatus(ComputerStatus.READY)
+ }
+
+ fun pulse(instructions: Long): Long {
+ var count: Long = 0
+
+ var i: Long = 0
+ while (i < instructions && status == ComputerStatus.EXECUTING) {
+ fetchAndExecute()
+ count++
+
+ if (breakpoints[getRegisterValue(Register.PC).toInt()].toInt() != 0) {
+ setStatus(ComputerStatus.BREAK)
+ }
+ i++
+ }
+
+ return count
+ }
+
+ fun run() {
+ setStatus(ComputerStatus.EXECUTING)
+ clock.run()
+ }
+
+ fun back() {
+ clock.back()
+ }
+
+ fun step() {
+ clock.step()
+ }
+
+ fun setStatus(status: ComputerStatus?) {
+ this.status = status
+ this.notifyOfExecutionUpdate()
+
+ if (status == ComputerStatus.FINISHED || status == ComputerStatus.BREAK) {
+ this.notifyOfStepExecution()
+ }
+ }
+
+ fun getStatus(): ComputerStatus? {
+ return status
+ }
+
+ fun fetchAndExecute() {
+ currentRewindStep = RewindStep()
+ rewindSteps!!.push(currentRewindStep)
+ fetchCurrentInstruction()
+ val instruction = getRegisterValue(Register.IR)
+ if (isDoubleWordInstruction(instruction)) {
+ setRegisterValue(
+ Register.PC,
+ (getRegisterValue(Register.PC) + 2 * WORD_SIZE).toShort().toInt()
+ )
+ } else {
+ setRegisterValue(Register.PC, (getRegisterValue(Register.PC) + WORD_SIZE).toShort().toInt())
+ }
+ execInstruction(instruction)
+ }
+
+ fun execInstruction(instruction: Short) {
+ observers!!.forEach(Consumer { o: MTMCObserver? -> o!!.beforeExecution(instruction) })
+ val instructionType: Short = getBits(16, 4, instruction)
+ if (instructionType.toInt() == 0x0000) { // MISC
+ val topNibble: Int = getBits(12, 4, instruction).toInt()
+ when (topNibble) {
+ 0 -> {
+ // sys call
+ val sysCall: Short = getBits(8, 8, instruction)
+ os.handleSysCall(sysCall)
+ }
+
+ 1 -> {
+ // mov
+ val to: Short = getBits(8, 4, instruction)
+ val from: Short = getBits(4, 4, instruction)
+ val value = getRegisterValue(from.toInt())
+ setRegisterValue(to.toInt(), value.toInt())
+ }
+
+ 2 -> {
+ // inc
+ val target: Short = getBits(8, 4, instruction)
+ val immediateValue: Short = getBits(4, 4, instruction)
+ val registerValue = getRegisterValue(target.toInt())
+ val value = registerValue + immediateValue
+ setRegisterValue(target.toInt(), value)
+ }
+
+ 3 -> {
+ // dec
+ val target: Short = getBits(8, 4, instruction)
+ val immediateValue: Short = getBits(4, 4, instruction)
+ val registerValue = getRegisterValue(target.toInt())
+ val value = registerValue - immediateValue
+ setRegisterValue(target.toInt(), value)
+ }
+
+ 4 -> {
+ // dec
+ val target: Short = getBits(8, 4, instruction)
+ val immediateValue: Short = getBits(4, 4, instruction)
+ setRegisterValue(target.toInt(), immediateValue.toInt())
+ }
+
+ 5 -> {
+ // mcp
+ var source: Short = getBits(8, 4, instruction)
+ source = getRegisterValue(source.toInt())
+ var dest: Short = getBits(4, 4, instruction)
+ dest = getRegisterValue(dest.toInt())
+ val size = getRegisterValue(Register.DR).toInt()
+ for (i in 0.. {
+ // debug
+ val debugIndex: Short = getBits(8, 8, instruction)
+ debugInfo!!.handleDebugString(debugIndex, this)
+ }
+
+ 15 -> {
+ // noop
+ }
+
+ else -> badInstruction(instruction)
+ }
+ } else if (instructionType.toInt() == 0x0001) { // ALU
+ var opCode: Short = getBits(12, 4, instruction)
+
+ val targetReg: Short = getBits(8, 4, instruction)
+ val sourceReg: Short
+
+ if (opCode.toInt() == 15) {
+ // immediate, source is data register, opcode is the lowest nibble
+ sourceReg = Register.DR.ordinal.toShort()
+ opCode = getBits(4, 4, instruction)
+ } else if (opCode.toInt() == 12 || opCode.toInt() == 13 || opCode.toInt() == 14) {
+ // unary
+ sourceReg = targetReg
+ } else {
+ // binary op, source is in the lowest nibble
+ sourceReg = getBits(4, 4, instruction)
+ }
+
+ val sourceValue = getRegisterValue(sourceReg.toInt())
+ val targetValue = getRegisterValue(targetReg.toInt())
+
+ var result = 0
+ when (opCode.toInt()) {
+ 0 -> {
+ result = targetValue + sourceValue
+ setRegisterValue(targetReg.toInt(), result)
+ }
+
+ 1 -> {
+ // sub
+ result = targetValue - sourceValue
+ setRegisterValue(targetReg.toInt(), result)
+ }
+
+ 2 -> {
+ // mul
+ result = targetValue * sourceValue
+ setRegisterValue(targetReg.toInt(), result)
+ }
+
+ 3 -> {
+ // div
+ result = targetValue / sourceValue
+ setRegisterValue(targetReg.toInt(), result)
+ }
+
+ 4 -> {
+ // mod
+ result = targetValue % sourceValue
+ setRegisterValue(targetReg.toInt(), result)
+ }
+
+ 5 -> {
+ // and
+ result = targetValue.toInt() and sourceValue.toInt()
+ setRegisterValue(targetReg.toInt(), result)
+ }
+
+ 6 -> {
+ // or
+ result = targetValue.toInt() or sourceValue.toInt()
+ setRegisterValue(targetReg.toInt(), result)
+ }
+
+ 7 -> {
+ // xor
+ result = targetValue.toInt() xor sourceValue.toInt()
+ setRegisterValue(targetReg.toInt(), result)
+ }
+
+ 8 -> {
+ // shift left
+ result = targetValue.toInt() shl sourceValue.toInt()
+ setRegisterValue(targetReg.toInt(), result)
+ }
+
+ 9 -> {
+ result = targetValue.toInt() ushr sourceValue.toInt()
+ setRegisterValue(targetReg.toInt(), result)
+ }
+
+ 10 -> {
+ result = min(targetValue.toInt(), sourceValue.toInt())
+ setRegisterValue(targetReg.toInt(), result)
+ }
+
+ 11 -> {
+ result = max(targetValue.toInt(), sourceValue.toInt())
+ setRegisterValue(targetReg.toInt(), result)
+ }
+
+ 12 -> {
+ result = targetValue.inv().toInt()
+ setRegisterValue(targetReg.toInt(), result.toShort().toInt())
+ }
+
+ 13 -> {
+ result = if (targetValue.toInt() == 0) 1 else 0
+ setRegisterValue(targetReg.toInt(), result)
+ }
+
+ 14 -> {
+ // negate
+ result = -targetValue
+ setRegisterValue(targetReg.toInt(), result.toShort().toInt())
+ }
+
+ else -> badInstruction(instruction)
+ }
+
+ setFlagTestBit(result != 0)
+ } else if (instructionType.toInt() == 2) {
+ val opcode: Short = getBits(12, 4, instruction)
+ val stackReg: Short = getBits(4, 4, instruction)
+ when (opcode.toInt()) {
+ 0 -> {
+ // push
+ val sourceRegister: Short = getBits(8, 4, instruction)
+ // decrement the stack pointer
+ setRegisterValue(stackReg.toInt(), getRegisterValue(stackReg.toInt()) - WORD_SIZE)
+ // write the value out to the location
+ val stackPointerValue = getRegisterValue(stackReg.toInt())
+ val valueToPush = getRegisterValue(sourceRegister.toInt())
+ writeWordToMemory(stackPointerValue.toInt(), valueToPush.toInt())
+ }
+
+ 1 -> {
+ // pop
+ val targetRegister: Short = getBits(8, 4, instruction)
+ val stackPointerValue = getRegisterValue(stackReg.toInt())
+ val value = fetchWordFromMemory(stackPointerValue.toInt())
+ setRegisterValue(targetRegister.toInt(), value.toInt())
+ setRegisterValue(stackReg.toInt(), getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ }
+
+ 2 -> {
+ // dup
+ val currentVal = fetchWordFromMemory(getRegisterValue(stackReg.toInt()).toInt())
+ setRegisterValue(stackReg.toInt(), getRegisterValue(stackReg.toInt()) - WORD_SIZE)
+ writeWordToMemory(getRegisterValue(stackReg.toInt()).toInt(), currentVal.toInt())
+ }
+
+ 3 -> {
+ // swap
+ val currentTop = fetchWordFromMemory(getRegisterValue(stackReg.toInt()).toInt())
+ val nextDown = fetchWordFromMemory(getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ writeWordToMemory(getRegisterValue(stackReg.toInt()).toInt(), nextDown.toInt())
+ writeWordToMemory(getRegisterValue(stackReg.toInt()) + WORD_SIZE, currentTop.toInt())
+ }
+
+ 4 -> // drop
+ setRegisterValue(stackReg.toInt(), getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+
+ 5 -> {
+ // over
+ val nextDown = fetchWordFromMemory(getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ setRegisterValue(stackReg.toInt(), getRegisterValue(stackReg.toInt()) - WORD_SIZE)
+ // write the value out to the location
+ writeWordToMemory(getRegisterValue(stackReg.toInt()).toInt(), nextDown.toInt())
+ }
+
+ 6 -> {
+ // rot
+ val currentTop = fetchWordFromMemory(getRegisterValue(stackReg.toInt()).toInt())
+ val nextDown = fetchWordFromMemory(getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ val thirdDown = fetchWordFromMemory(getRegisterValue(stackReg.toInt()) + 2 * WORD_SIZE)
+ writeWordToMemory(getRegisterValue(stackReg.toInt()).toInt(), thirdDown.toInt())
+ writeWordToMemory(getRegisterValue(stackReg.toInt()) + WORD_SIZE, currentTop.toInt())
+ writeWordToMemory(getRegisterValue(stackReg.toInt()) + 2 * WORD_SIZE, nextDown.toInt())
+ }
+
+ 7 -> {
+ // sop
+ val aluOpCode: Short = getBits(8, 4, instruction)
+ when (aluOpCode.toInt()) {
+ 0 -> {
+ val currentTop = fetchWordFromMemory(getRegisterValue(stackReg.toInt()).toInt())
+ val nextDown = fetchWordFromMemory(getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ setRegisterValue(stackReg.toInt(), getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ val value = nextDown + currentTop
+ writeWordToMemory(getRegisterValue(stackReg.toInt()).toInt(), value)
+ }
+
+ 1 -> {
+ val currentTop = fetchWordFromMemory(getRegisterValue(stackReg.toInt()).toInt())
+ val nextDown = fetchWordFromMemory(getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ setRegisterValue(stackReg.toInt(), getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ val value = nextDown - currentTop
+ writeWordToMemory(getRegisterValue(stackReg.toInt()).toInt(), value)
+ }
+
+ 2 -> {
+ val currentTop = fetchWordFromMemory(getRegisterValue(stackReg.toInt()).toInt())
+ val nextDown = fetchWordFromMemory(getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ setRegisterValue(stackReg.toInt(), getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ val value = nextDown * currentTop
+ writeWordToMemory(getRegisterValue(stackReg.toInt()).toInt(), value)
+ }
+
+ 3 -> {
+ val currentTop = fetchWordFromMemory(getRegisterValue(stackReg.toInt()).toInt())
+ val nextDown = fetchWordFromMemory(getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ setRegisterValue(stackReg.toInt(), getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ val value = nextDown / currentTop
+ writeWordToMemory(getRegisterValue(stackReg.toInt()).toInt(), value)
+ }
+
+ 4 -> {
+ val currentTop = fetchWordFromMemory(getRegisterValue(stackReg.toInt()).toInt())
+ val nextDown = fetchWordFromMemory(getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ setRegisterValue(stackReg.toInt(), getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ val value = nextDown % currentTop
+ writeWordToMemory(getRegisterValue(stackReg.toInt()).toInt(), value)
+ }
+
+ 5 -> {
+ val currentTop = fetchWordFromMemory(getRegisterValue(stackReg.toInt()).toInt())
+ val nextDown = fetchWordFromMemory(getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ setRegisterValue(stackReg.toInt(), getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ val value = nextDown.toInt() and currentTop.toInt()
+ writeWordToMemory(getRegisterValue(stackReg.toInt()).toInt(), value)
+ }
+
+ 6 -> {
+ val currentTop = fetchWordFromMemory(getRegisterValue(stackReg.toInt()).toInt())
+ val nextDown = fetchWordFromMemory(getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ setRegisterValue(stackReg.toInt(), getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ val value = nextDown.toInt() or currentTop.toInt()
+ writeWordToMemory(getRegisterValue(stackReg.toInt()).toInt(), value)
+ }
+
+ 7 -> {
+ val currentTop = fetchWordFromMemory(getRegisterValue(stackReg.toInt()).toInt())
+ val nextDown = fetchWordFromMemory(getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ setRegisterValue(stackReg.toInt(), getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ val value = nextDown.toInt() xor currentTop.toInt()
+ writeWordToMemory(getRegisterValue(stackReg.toInt()).toInt(), value)
+ }
+
+ 8 -> {
+ val currentTop = fetchWordFromMemory(getRegisterValue(stackReg.toInt()).toInt())
+ val nextDown = fetchWordFromMemory(getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ setRegisterValue(stackReg.toInt(), getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ val value = nextDown.toInt() shl currentTop.toInt()
+ writeWordToMemory(getRegisterValue(stackReg.toInt()).toInt(), value)
+ }
+
+ 9 -> {
+ val currentTop = fetchWordFromMemory(getRegisterValue(stackReg.toInt()).toInt())
+ val nextDown = fetchWordFromMemory(getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ setRegisterValue(stackReg.toInt(), getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ val value = nextDown.toInt() ushr currentTop.toInt()
+ writeWordToMemory(getRegisterValue(stackReg.toInt()).toInt(), value)
+ }
+
+ 10 -> {
+ val currentTop = fetchWordFromMemory(getRegisterValue(stackReg.toInt()).toInt())
+ val nextDown = fetchWordFromMemory(getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ setRegisterValue(stackReg.toInt(), getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ val min = min(currentTop.toInt(), nextDown.toInt())
+ writeWordToMemory(getRegisterValue(stackReg.toInt()).toInt(), min)
+ }
+
+ 11 -> {
+ val currentTop = fetchWordFromMemory(getRegisterValue(stackReg.toInt()).toInt())
+ val nextDown = fetchWordFromMemory(getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ setRegisterValue(stackReg.toInt(), getRegisterValue(stackReg.toInt()) + WORD_SIZE)
+ val max = max(currentTop.toInt(), nextDown.toInt())
+ writeWordToMemory(getRegisterValue(stackReg.toInt()).toInt(), max)
+ }
+
+ 12 -> {
+ val currentTop = fetchWordFromMemory(getRegisterValue(stackReg.toInt()).toInt())
+ val value = currentTop.toInt().inv()
+ writeWordToMemory(getRegisterValue(stackReg.toInt()).toInt(), value)
+ }
+
+ 13 -> {
+ val currentTop = fetchWordFromMemory(getRegisterValue(stackReg.toInt()).toInt())
+ val value = if (currentTop.toInt() == 0) 1 else 0
+ writeWordToMemory(getRegisterValue(stackReg.toInt()).toInt(), value)
+ }
+
+ 14 -> {
+ val currentTop = fetchWordFromMemory(getRegisterValue(stackReg.toInt()).toInt())
+ val value = -currentTop.toInt()
+ writeWordToMemory(getRegisterValue(stackReg.toInt()).toInt(), value)
+ }
+
+ else -> badInstruction(instruction)
+ }
+ }
+
+ 15 -> {
+ val immediateValue = getRegisterValue(Register.DR)
+ setRegisterValue(stackReg.toInt(), getRegisterValue(stackReg.toInt()) - WORD_SIZE)
+ writeWordToMemory(getRegisterValue(stackReg.toInt()).toInt(), immediateValue.toInt())
+ }
+
+ else -> badInstruction(instruction)
+ }
+ } else if (instructionType.toInt() == 3) {
+ val opCode: Int = getBits(12, 4, instruction).toInt()
+ val lhs: Int = getBits(8, 4, instruction).toInt()
+ val rhs: Int = getBits(4, 4, instruction).toInt()
+ when (opCode) {
+ 0 -> {
+ val lhsVal = getRegisterValue(lhs)
+ val rhsVal = getRegisterValue(rhs)
+ setFlagTestBit(lhsVal == rhsVal)
+ }
+
+ 1 -> {
+ val lhsVal = getRegisterValue(lhs)
+ val rhsVal = getRegisterValue(rhs)
+ setFlagTestBit(lhsVal != rhsVal)
+ }
+
+ 2 -> {
+ val lhsVal = getRegisterValue(lhs)
+ val rhsVal = getRegisterValue(rhs)
+ setFlagTestBit(lhsVal > rhsVal)
+ }
+
+ 3 -> {
+ val lhsVal = getRegisterValue(lhs)
+ val rhsVal = getRegisterValue(rhs)
+ setFlagTestBit(lhsVal >= rhsVal)
+ }
+
+ 4 -> {
+ val lhsVal = getRegisterValue(lhs)
+ val rhsVal = getRegisterValue(rhs)
+ setFlagTestBit(lhsVal < rhsVal)
+ }
+
+ 5 -> {
+ val lhsVal = getRegisterValue(lhs)
+ val rhsVal = getRegisterValue(rhs)
+ setFlagTestBit(lhsVal <= rhsVal)
+ }
+
+ 8 -> {
+ val lhsVal = getRegisterValue(lhs)
+ val rhsVal = rhs.toShort()
+ setFlagTestBit(lhsVal == rhsVal)
+ }
+
+ 9 -> {
+ val lhsVal = getRegisterValue(lhs)
+ val rhsVal = rhs.toShort()
+ setFlagTestBit(lhsVal != rhsVal)
+ }
+
+ 10 -> {
+ val lhsVal = getRegisterValue(lhs)
+ val rhsVal = rhs.toShort()
+ setFlagTestBit(lhsVal > rhsVal)
+ }
+
+ 11 -> {
+ val lhsVal = getRegisterValue(lhs)
+ val rhsVal = rhs.toShort()
+ setFlagTestBit(lhsVal >= rhsVal)
+ }
+
+ 12 -> {
+ val lhsVal = getRegisterValue(lhs)
+ val rhsVal = rhs.toShort()
+ setFlagTestBit(lhsVal < rhsVal)
+ }
+
+ 13 -> {
+ val lhsVal = getRegisterValue(lhs)
+ val rhsVal = rhs.toShort()
+ setFlagTestBit(lhsVal <= rhsVal)
+ }
+
+ else -> badInstruction(instruction)
+ }
+ } else if (8 == instructionType.toInt()) {
+ // load/store
+ val opCode: Int = getBits(12, 4, instruction).toInt()
+ val reg: Int = getBits(8, 4, instruction).toInt()
+ val offsetReg: Int = getBits(4, 4, instruction).toInt()
+ val address = getRegisterValue(Register.DR).toInt()
+ when (opCode) {
+ 0 -> {
+ val value = fetchWordFromMemory(address)
+ setRegisterValue(reg, value.toInt())
+ }
+
+ 1 -> {
+ val value = fetchWordFromMemory(address + getRegisterValue(offsetReg))
+ setRegisterValue(reg, value.toInt())
+ }
+
+ 2 -> {
+ val value = fetchByteFromMemory(address).toShort()
+ setRegisterValue(reg, value.toInt())
+ }
+
+ 3 -> {
+ val value = fetchByteFromMemory(address + getRegisterValue(offsetReg)).toShort()
+ setRegisterValue(reg, value.toInt())
+ }
+
+ 4 -> {
+ val value = getRegisterValue(reg)
+ writeWordToMemory(address, value.toInt())
+ }
+
+ 5 -> {
+ val value = getRegisterValue(reg)
+ writeWordToMemory(address + getRegisterValue(offsetReg), value.toInt())
+ }
+
+ 6 -> {
+ val value = getRegisterValue(reg).toByte()
+ writeByteToMemory(address, value)
+ }
+
+ 7 -> {
+ val value = getRegisterValue(reg).toByte()
+ writeByteToMemory(address + getRegisterValue(offsetReg), value)
+ }
+
+ 15 -> {
+ setRegisterValue(reg, address)
+ }
+
+ else -> badInstruction(instruction)
+ }
+ } else if (4 <= instructionType && instructionType <= 7) {
+ // load/store register
+ val loadStoreType: Short = getBits(14, 2, instruction)
+ val targetRegister: Short = getBits(12, 4, instruction)
+ val addressRegister: Short = getBits(8, 4, instruction)
+ val offsetRegister: Short = getBits(4, 4, instruction)
+ val targetAddress =
+ getRegisterValue(addressRegister.toInt()) + getRegisterValue(offsetRegister.toInt())
+ if (loadStoreType.toInt() == 0x0) {
+ setRegisterValue(targetRegister.toInt(), fetchWordFromMemory(targetAddress).toInt())
+ } else if (loadStoreType.toInt() == 0x1) {
+ setRegisterValue(targetRegister.toInt(), fetchByteFromMemory(targetAddress).toInt())
+ } else if (loadStoreType.toInt() == 0x2) {
+ writeWordToMemory(targetAddress, getRegisterValue(targetRegister.toInt()).toInt())
+ } else if (loadStoreType.toInt() == 0x3) {
+ writeByteToMemory(targetAddress, getRegisterValue(targetRegister.toInt()).toByte())
+ }
+ } else if (9 == instructionType.toInt()) {
+ // jump reg
+ val reg: Short = getBits(4, 4, instruction)
+ val location = getRegisterValue(reg.toInt())
+ setRegisterValue(Register.PC, location.toInt())
+ } else if (12 <= instructionType && instructionType <= 15) {
+ // jumps
+ val jumpType: Short = getBits(14, 2, instruction)
+ if (jumpType.toInt() == 0) {
+ // unconditional
+ val location: Short = getBits(12, 12, instruction)
+ setRegisterValue(Register.PC, location.toInt())
+ } else if (jumpType.toInt() == 1) {
+ // jz
+ val location: Short = getBits(12, 12, instruction)
+ if (!this.isFlagTestBitSet) {
+ setRegisterValue(Register.PC, location.toInt())
+ }
+ } else if (jumpType.toInt() == 2) {
+ // jnz
+ val location: Short = getBits(12, 12, instruction)
+ if (this.isFlagTestBitSet) {
+ setRegisterValue(Register.PC, location.toInt())
+ }
+ } else if (jumpType.toInt() == 3) {
+ // jump & link
+ val location: Short = getBits(12, 12, instruction)
+ setRegisterValue(Register.RA, getRegisterValue(Register.PC).toInt())
+ setRegisterValue(Register.PC, location.toInt())
+ }
+ } else {
+ badInstruction(instruction)
+ }
+ observers.forEach(Consumer { o: MTMCObserver? -> o!!.afterExecution(instruction) })
+ }
+
+ val isFlagTestBitSet: Boolean
+ get() {
+ val i = getRegisterValue(Register.FLAGS).toInt() and 1
+ val b = i != 0
+ return b
+ }
+
+ fun setFlagTestBit(testVal: Boolean) {
+ var value = getRegisterValue(Register.FLAGS)
+ value = if (testVal) {
+ value or 1
+ } else {
+ value and 14
+ }
+ setRegisterValue(Register.FLAGS, value.toInt())
+ }
+
+ private fun badInstruction(instruction: Short) {
+ setStatus(ComputerStatus.PERMANENT_ERROR)
+ // TODO implement flags
+ console.println("BAD INSTRUCTION: 0x" + Integer.toHexString(instruction.toInt() and 0xFFFF))
+ }
+
+ fun fetchCurrentInstruction() {
+ val pc = getRegisterValue(Register.PC)
+ val instruction = fetchWordFromMemory(pc.toInt())
+ setRegisterValue(Register.IR, instruction.toInt())
+ if (isDoubleWordInstruction(instruction)) {
+ val data = fetchWordFromMemory(pc + WORD_SIZE)
+ setRegisterValue(Register.DR, data.toInt())
+ } else {
+ setRegisterValue(Register.DR, 0)
+ }
+ observers!!.forEach(Consumer { o: MTMCObserver? -> o!!.instructionFetched(instruction) })
+ }
+
+ fun fetchWordFromMemory(address: Int): Short {
+ val upperByte = fetchByteFromMemory(address).toShort()
+ val lowerByte = fetchByteFromMemory(address + 1)
+ var value = (upperByte.toInt() shl 8).toShort()
+ val i = value.toInt() or Byte.toUnsignedInt(lowerByte)
+ value = i.toShort()
+ return value
+ }
+
+ fun fetchByteFromMemory(address: Int): kotlin.Byte {
+ if (address < 0 || address >= memory.size) {
+ setStatus(ComputerStatus.PERMANENT_ERROR)
+ console.println(
+ "BAD MEMORY LOCATION ON READ: " + address + " (0x" + Integer.toHexString(
+ address and 0xFFFF
+ ) + ")"
+ )
+ return 0
+ } else {
+ return memory[address]
+ }
+ }
+
+ fun writeWordToMemory(address: Int, value: Int) {
+ val i = (value ushr 8).toByte()
+ writeByteToMemory(address, i)
+ writeByteToMemory(address + 1, (value and 255).toByte())
+ }
+
+ fun writeByteToMemory(address: Int, value: kotlin.Byte) {
+ if (address < 0 || address >= memory.size) {
+ setStatus(ComputerStatus.PERMANENT_ERROR)
+ console.println(
+ "BAD MEMORY LOCATION ON WRITE: " + address + " (0x" + Integer.toHexString(
+ address and 0xFFFF
+ ) + ")"
+ )
+ return
+ }
+ val currentValue = memory[address]
+ addRewindStep(Runnable { memory[address] = currentValue })
+ memory[address] = value
+ observers!!.forEach(Consumer { o: MTMCObserver? -> o!!.memoryUpdated(address, value) })
+ }
+
+ private fun addRewindStep(runnable: Runnable?) {
+ if (currentRewindStep != null && rewindSteps != null) {
+ currentRewindStep!!.addSubStep(runnable)
+ if (rewindSteps!!.size > MAX_REWIND_STEPS) {
+ rewindSteps!!.removeLast()
+ }
+ }
+ }
+
+ fun setRegisterValue(register: Register, value: Int) {
+ setRegisterValue(register.ordinal, value)
+ }
+
+ fun setRegisterValue(register: Int, value: Int) {
+ if (Short.Companion.MAX_VALUE < value || value < Short.Companion.MIN_VALUE) {
+ // TODO mark as overflow
+ }
+ val currentValue = registerFile[register]
+ addRewindStep(Runnable {
+ registerFile[register] = currentValue
+ })
+ registerFile[register] = value.toShort()
+ observers!!.forEach(Consumer { o: MTMCObserver? -> o!!.registerUpdated(register, value) })
+ }
+
+ fun getRegisterValue(register: Register): Short {
+ return registerFile[register.ordinal]
+ }
+
+ fun getRegisterValue(register: Int): Short {
+ return registerFile[register]
+ }
+
+ fun getBreakpoints(): IntArray {
+ val list = ArrayList()
+
+ for (i in breakpoints.indices) {
+ if (breakpoints[i].toInt() != 0) list.add(i)
+ }
+
+ val result = IntArray(list.size)
+
+ for (i in result.indices) result[i] = list.get(i)!!
+
+ return result
+ }
+
+ fun setBreakpoint(address: Int, active: Boolean) {
+ breakpoints[address] = (if (active) 1.toByte() else 0.toByte())
+ }
+
+ private fun start() {
+ console.start() // start the interactive console
+ }
+
+ fun getBytesFromMemory(address: Int, length: Int): ByteArray {
+ return Arrays.copyOfRange(memory, address, address + length)
+ }
+
+ val oS: MTOS
+ get() = os
+
+ val memoryAddresses: Iterable
+ get() = Iterable {
+ IntStream.range(
+ 0,
+ MEMORY_SIZE
+ ).iterator()
+ }
+
+ fun addObserver(observer: MTMCObserver?) {
+ observers!!.add(observer!!)
+ }
+
+ fun removeObserver(observer: MTMCObserver?) {
+ observers!!.remove(observer!!)
+ }
+
+ fun pause() {
+ setStatus(ComputerStatus.READY)
+ }
+
+ fun notifyOfConsoleUpdate() {
+ for (observer in observers) {
+ observer.consoleUpdated()
+ }
+ }
+
+ fun notifyOfConsolePrinting() {
+ for (observer in observers) {
+ observer.consolePrinting()
+ }
+ }
+
+ fun notifyOfDisplayUpdate() {
+ for (observer in observers) {
+ observer.displayUpdated()
+ }
+ }
+
+ fun notifyOfFileSystemUpdate() {
+ for (observer in observers) {
+ observer.filesystemUpdated()
+ }
+ }
+
+ fun notifyOfExecutionUpdate() {
+ for (observer in observers) {
+ observer.executionUpdated()
+ }
+ }
+
+ fun notifyOfStepExecution() {
+ for (observer in observers) {
+ observer.stepExecution()
+ }
+ }
+
+ fun notifyOfRequestString() {
+ for (observer in observers) {
+ observer.requestString()
+ }
+ }
+
+ fun notifyOfRequestCharacter() {
+ for (observer in observers) {
+ observer.requestCharacter()
+ }
+ }
+
+ fun notifyOfRequestInteger() {
+ for (observer in observers) {
+ observer.requestInteger()
+ }
+ }
+
+ val iOState: Int
+ get() = iO.value
+
+ fun setArg(arg: String) {
+ if (!arg.isEmpty()) {
+ val start = getRegisterValue(Register.BP)
+ val bytes = arg.toByteArray()
+ writeStringToMemory(start.toInt(), bytes)
+ setRegisterValue(Register.A0, start.toInt())
+ setRegisterValue(Register.BP, start + bytes.size + 1)
+ }
+ }
+
+ fun writeStringToMemory(start: Int, bytes: ByteArray) {
+ for (i in bytes.indices) {
+ val aByte = bytes[i]
+ writeByteToMemory(start + i, aByte)
+ }
+ // null terminate
+ writeByteToMemory(start + bytes.size, 0.toByte())
+ }
+
+ fun rewind() {
+ val latestRewindStep = rewindSteps!!.pop()
+ latestRewindStep.rewind()
+ }
+
+ val isBackAvailable: Boolean
+ get() = !rewindSteps!!.isEmpty()
+
+ enum class ComputerStatus {
+ READY,
+ EXECUTING,
+ PERMANENT_ERROR,
+ FINISHED,
+ WAITING,
+ BREAK
+ }
+
+ override fun toString(): String {
+ val s = StringBuilder()
+ s.append("CI: ").append(
+ Instruction.disassemble(
+ getRegisterValue(Register.IR), getRegisterValue(
+ Register.DR
+ )
+ )
+ )
+ s.append(", Status: ").append(getStatus()!!.name)
+ val fp = getRegisterValue(Register.FP)
+ val sp = getRegisterValue(Register.SP)
+ s.append(", FP:SP=").append(fp.toInt()).append(':').append(sp.toInt())
+ s.append(", STACK: ")
+
+ for (i in fp downTo sp + 1) {
+ val value = getBytesFromMemory(i - 2, 2)
+ val v = value[1].toInt() ushr 8 or value[0].toInt()
+ s.append(v).append(' ')
+ }
+
+ return s.toString()
+ }
+
+ companion object {
+ // constants
+ const val WORD_SIZE: Short = 2
+ const val MEMORY_SIZE: Int = 4096
+
+ const val MAX_REWIND_STEPS: Int = 100
+
+ fun isDoubleWordInstruction(instruction: Short): Boolean {
+ val isLoadStore = getBits(16, 4, instruction) == 8.toShort()
+ if (isLoadStore) {
+ return true
+ }
+ val isALUImmediate = getBits(16, 8, instruction) == 31.toShort()
+ if (isALUImmediate) {
+ return true
+ }
+ val isPushImmediate = getBits(16, 8, instruction) == 47.toShort()
+ if (isPushImmediate) {
+ return true
+ }
+ val isMcp = getBits(16, 8, instruction) == 5.toShort()
+ if (isMcp) {
+ return true
+ }
+ return false
+ }
+
+ @JvmStatic
+ fun main(args: Array) {
+ val computer = MonTanaMiniComputer()
+ computer.speed = 1 // default to 1hz
+ computer.start()
+ }
+ }
+}
diff --git a/src/jvmMain/kotlin/mtmc/emulator/Register.kt b/src/jvmMain/kotlin/mtmc/emulator/Register.kt
new file mode 100644
index 0000000..20b8a0f
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/emulator/Register.kt
@@ -0,0 +1,76 @@
+package mtmc.emulator
+
+enum class Register {
+
+ //=== user-facing registers
+ T0, // temp registers
+ T1,
+ T2,
+ T3,
+ T4,
+ T5,
+ A0, // arg registers
+ A1,
+ A2,
+ A3,
+ RV, // return value
+ RA, // return address
+ FP, // frame pointer
+ SP, // stack pointer
+ BP, // break pointer
+ PC,
+
+ //=== non-user-facing registers
+ IR, // instruction register
+ DR, // data register
+ CB, // code boundary
+ DB, // data boundary
+ IO, // I/O register
+ FLAGS; // flags register
+
+ companion object {
+ fun toInteger(reg: String): Int {
+ return valueOf(reg.uppercase()).ordinal
+ }
+
+ fun fromInteger(reg: Int): String {
+ return values()[reg].name.lowercase()
+ }
+
+ fun isWriteable(reg: Int): Boolean {
+ return reg in 0..15
+ }
+
+ fun isReadable(reg: Int): Boolean {
+ return reg in 0..15
+ }
+
+ private fun isTempRegister(reg: Int): Boolean {
+ return reg in 0..5
+ }
+
+ fun isWriteable(register: String): Boolean {
+ return try {
+ isWriteable(toInteger(register))
+ } catch (e: Exception) {
+ false
+ }
+ }
+
+ fun isReadable(register: String): Boolean {
+ return try {
+ isReadable(toInteger(register))
+ } catch (e: Exception) {
+ false
+ }
+ }
+
+ fun isTempRegister(register: String): Boolean {
+ return try {
+ isTempRegister(toInteger(register))
+ } catch (e: Exception) {
+ false
+ }
+ }
+ }
+}
diff --git a/src/jvmMain/kotlin/mtmc/emulator/RewindStep.kt b/src/jvmMain/kotlin/mtmc/emulator/RewindStep.kt
new file mode 100644
index 0000000..bfa8b49
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/emulator/RewindStep.kt
@@ -0,0 +1,13 @@
+package mtmc.emulator
+
+class RewindStep {
+ var subSteps: MutableList = ArrayList()
+
+ fun rewind() {
+ subSteps.reversed().forEach({ obj: Runnable? -> obj!!.run() })
+ }
+
+ fun addSubStep(subStep: Runnable?) {
+ subSteps.add(subStep)
+ }
+}
diff --git a/src/jvmMain/kotlin/mtmc/lang/CompilationException.java b/src/jvmMain/kotlin/mtmc/lang/CompilationException.java
new file mode 100644
index 0000000..c23187a
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/lang/CompilationException.java
@@ -0,0 +1,19 @@
+package mtmc.lang;
+
+public class CompilationException extends Exception {
+ protected Span span;
+
+ public CompilationException(String message, Span span) {
+ super(message);
+ this.span = span;
+ }
+
+ public CompilationException(CompilationException parent, String message) {
+ super(message, parent);
+ this.span = parent.span;
+ }
+
+ public Span getSpan() {
+ return span;
+ }
+}
diff --git a/src/jvmMain/kotlin/mtmc/lang/Language.java b/src/jvmMain/kotlin/mtmc/lang/Language.java
new file mode 100644
index 0000000..cbc402a
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/lang/Language.java
@@ -0,0 +1,7 @@
+package mtmc.lang;
+
+import mtmc.os.exec.Executable;
+
+public interface Language {
+ Executable compileExecutable(String filename, String source) throws ParseException, CompilationException;
+}
diff --git a/src/jvmMain/kotlin/mtmc/lang/Location.java b/src/jvmMain/kotlin/mtmc/lang/Location.java
new file mode 100644
index 0000000..28cb477
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/lang/Location.java
@@ -0,0 +1,55 @@
+package mtmc.lang;
+
+public record Location(int index) {
+
+ public static int[] getLineNos(String source, int... indices) {
+ int[] out = new int[indices.length];
+ int index = 0, line = 1;
+ while (index < source.length()) {
+ for (int i = 0; i < indices.length; i++) {
+ if (indices[i] == index) {
+ out[i] = line;
+ }
+ }
+
+ int cp = source.charAt(index);
+ if (cp == '\n') {
+ line += 1;
+ }
+ index += Character.charCount(cp);
+ }
+ return out;
+ }
+
+ public LineInfo getLineInfo(String source) {
+ int index = 0, lineStart = 0;
+ int lineno = 1;
+ int column = 1;
+ while (index < source.length()) {
+ if (index == this.index) {
+ break;
+ }
+ int cp = source.charAt(index);
+
+ if (cp == '\n') {
+ lineno += 1;
+ lineStart = index + 1;
+ } else {
+ column += 1;
+ }
+
+ index += Character.charCount(cp);
+ }
+
+ while (index < source.length()) {
+ int cp = source.charAt(index);
+ index += Character.charCount(cp);
+ if (cp == '\n') break;
+ }
+ String line = source.substring(lineStart, index);
+
+ return new LineInfo(lineno, column, line);
+ }
+
+ public record LineInfo(int lineno, int column, String line) {}
+}
diff --git a/src/jvmMain/kotlin/mtmc/lang/ParseException.java b/src/jvmMain/kotlin/mtmc/lang/ParseException.java
new file mode 100644
index 0000000..4549eb6
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/lang/ParseException.java
@@ -0,0 +1,43 @@
+package mtmc.lang;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class ParseException extends Exception {
+ public final List messages;
+
+ public ParseException(Message message, Message ...rest) {
+ var messages = new ArrayList(1 + rest.length);
+ messages.add(message);
+ messages.addAll(Arrays.asList(rest));
+ this.messages = Collections.unmodifiableList(messages);
+ }
+
+ public ParseException(ParseException parent, Message message, Message ...rest) {
+ var messages = new ArrayList( 1 + rest.length + parent.messages.size());
+ messages.add(message);
+ messages.addAll(Arrays.asList(rest));
+ messages.addAll(parent.messages);
+ this.messages = Collections.unmodifiableList(messages);
+ }
+
+ public record Message(Span span, String message) {
+ public Message(Token token, String message) {
+ this(Span.of(token), message);
+ }
+
+ public Token start() {
+ return span.start();
+ }
+
+ public Token end() {
+ return span.end();
+ }
+ }
+
+ public String report(String source) {
+ return "TODO: I ain't no snitch";
+ }
+}
diff --git a/src/jvmMain/kotlin/mtmc/lang/Span.java b/src/jvmMain/kotlin/mtmc/lang/Span.java
new file mode 100644
index 0000000..fa750c7
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/lang/Span.java
@@ -0,0 +1,17 @@
+package mtmc.lang;
+
+public record Span(Token start, Token end) {
+
+ public static Span of(Token token) {
+ return new Span(token, token);
+ }
+
+ public static Span of(Token start, Token end) {
+ return new Span(start, end);
+ }
+
+ public boolean isOnSingleLine(String source) {
+ int[] lines = Location.getLineNos(source, start.start(), end.end());
+ return lines[0] == lines[1];
+ }
+}
diff --git a/src/jvmMain/kotlin/mtmc/lang/Token.java b/src/jvmMain/kotlin/mtmc/lang/Token.java
new file mode 100644
index 0000000..67bdd99
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/lang/Token.java
@@ -0,0 +1,15 @@
+package mtmc.lang;
+
+public interface Token {
+ Location getStart();
+ Location getEnd();
+ String getContent();
+
+ default int start() {
+ return getStart().index();
+ }
+
+ default int end() {
+ return getEnd().index();
+ }
+}
diff --git a/src/jvmMain/kotlin/mtmc/lang/sea/SeaCompiler.java b/src/jvmMain/kotlin/mtmc/lang/sea/SeaCompiler.java
new file mode 100644
index 0000000..9d5c193
--- /dev/null
+++ b/src/jvmMain/kotlin/mtmc/lang/sea/SeaCompiler.java
@@ -0,0 +1,1415 @@
+package mtmc.lang.sea;
+
+import mtmc.asm.Assembler;
+import mtmc.lang.CompilationException;
+import mtmc.lang.Span;
+import mtmc.lang.sea.ast.Declaration;
+import mtmc.lang.sea.ast.DeclarationFunc;
+import mtmc.lang.sea.ast.DeclarationStruct;
+import mtmc.lang.sea.ast.DeclarationSyntaxError;
+import mtmc.lang.sea.ast.DeclarationTypedef;
+import mtmc.lang.sea.ast.DeclarationVar;
+import mtmc.lang.sea.ast.Expression;
+import mtmc.lang.sea.ast.ExpressionAccess;
+import mtmc.lang.sea.ast.ExpressionBin;
+import mtmc.lang.sea.ast.ExpressionCall;
+import mtmc.lang.sea.ast.ExpressionCast;
+import mtmc.lang.sea.ast.ExpressionChar;
+import mtmc.lang.sea.ast.ExpressionIdent;
+import mtmc.lang.sea.ast.ExpressionIndex;
+import mtmc.lang.sea.ast.ExpressionInitializer;
+import mtmc.lang.sea.ast.ExpressionInteger;
+import mtmc.lang.sea.ast.ExpressionParens;
+import mtmc.lang.sea.ast.ExpressionPostfix;
+import mtmc.lang.sea.ast.ExpressionPrefix;
+import mtmc.lang.sea.ast.ExpressionString;
+import mtmc.lang.sea.ast.ExpressionSyntaxError;
+import mtmc.lang.sea.ast.ExpressionTernary;
+import mtmc.lang.sea.ast.ExpressionTypeError;
+import mtmc.lang.sea.ast.Statement;
+import mtmc.lang.sea.ast.StatementBlock;
+import mtmc.lang.sea.ast.StatementBreak;
+import mtmc.lang.sea.ast.StatementContinue;
+import mtmc.lang.sea.ast.StatementDoWhile;
+import mtmc.lang.sea.ast.StatementExpression;
+import mtmc.lang.sea.ast.StatementFor;
+import mtmc.lang.sea.ast.StatementGoto;
+import mtmc.lang.sea.ast.StatementIf;
+import mtmc.lang.sea.ast.StatementReturn;
+import mtmc.lang.sea.ast.StatementSyntaxError;
+import mtmc.lang.sea.ast.StatementVar;
+import mtmc.lang.sea.ast.StatementWhile;
+import mtmc.lang.sea.ast.Unit;
+import mtmc.os.exec.Executable;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Stack;
+
+import static mtmc.lang.sea.SeaCompiler.MemLoc.RV;
+import static mtmc.lang.sea.SeaCompiler.MemLoc.SP;
+import static mtmc.lang.sea.SeaCompiler.MemLoc.STACK;
+import static mtmc.lang.sea.SeaCompiler.MemLoc.T0;
+import static mtmc.lang.sea.SeaCompiler.MemLoc.T1;
+import static mtmc.lang.sea.SeaCompiler.MemLoc.T2;
+import static mtmc.lang.sea.SeaCompiler.MemLoc.T4;
+import static mtmc.lang.sea.SeaCompiler.MemLoc.T5;
+import static mtmc.util.StringEscapeUtils.escapeString;
+
+public class SeaCompiler {
+ protected Unit program;
+
+ private StringBuilder data = new StringBuilder();
+ private StringBuilder code = new StringBuilder();
+ private HashMap globalLabels = new HashMap<>();
+ private HashMap