generated from rnentjes/kotlin-server-web-undertow
Compare commits
15 Commits
c74c0134ac
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 4dcfe3a6ff | |||
| f14f316e38 | |||
| 11b069ddc5 | |||
| d7e331728f | |||
| 8457d3a854 | |||
| 37691dc7fa | |||
| f169dce339 | |||
| 6acf781324 | |||
| 40baff5cb0 | |||
| 11da7fd588 | |||
| 4b17ce3cf5 | |||
| d5314ce046 | |||
| 9f295b2fb9 | |||
| c7552c2a95 | |||
| 63f9a1f928 |
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -3,7 +3,7 @@
|
|||||||
<component name="FrameworkDetectionExcludesConfiguration">
|
<component name="FrameworkDetectionExcludesConfiguration">
|
||||||
<file type="web" url="file://$PROJECT_DIR$" />
|
<file type="web" url="file://$PROJECT_DIR$" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="corretto-21" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="corretto-21" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
<component name="accountSettings">
|
<component name="accountSettings">
|
||||||
|
|||||||
7
.idea/runConfigurations/MTMC_debug.xml
generated
Normal file
7
.idea/runConfigurations/MTMC_debug.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="MTMC debug" type="JavascriptDebugType" uri="http://localhost:4001/">
|
||||||
|
<method v="2">
|
||||||
|
<option name="Gradle.BeforeRunTask" enabled="true" tasks="jsBrowserDevelopmentExecutableDistribution" externalProjectPath="$PROJECT_DIR$" vmOptions="" scriptParameters="" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
10
.idea/runConfigurations/MainKt.xml
generated
Normal file
10
.idea/runConfigurations/MainKt.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="MainKt" type="JetRunConfigurationType" nameIsGenerated="true">
|
||||||
|
<option name="MAIN_CLASS_NAME" value="mtmc.MainKt" />
|
||||||
|
<module name="mtmc-web.jvmMain" />
|
||||||
|
<shortenClasspath name="NONE" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
13
LICENSE.txt
Normal file
13
LICENSE.txt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
Zero-Clause BSD
|
||||||
|
=============
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for
|
||||||
|
any purpose with or without fee is hereby granted.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL
|
||||||
|
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
|
||||||
|
OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
|
||||||
|
FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
|
||||||
|
DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||||
|
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||||
|
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
117
build.gradle.kts
117
build.gradle.kts
@@ -1,70 +1,81 @@
|
|||||||
@file:OptIn(ExperimentalDistributionDsl::class)
|
@file:OptIn(ExperimentalDistributionDsl::class)
|
||||||
|
|
||||||
import org.codehaus.groovy.tools.shell.util.Logger.io
|
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
|
||||||
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalDistributionDsl
|
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalDistributionDsl
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("multiplatform") version "2.1.20"
|
kotlin("multiplatform") version "2.1.20"
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "nl.astraeus"
|
group = "nl.astraeus"
|
||||||
version = "0.1.0-SNAPSHOT"
|
version = "0.1.0-SNAPSHOT"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven {
|
maven {
|
||||||
url = uri("https://gitea.astraeus.nl/api/packages/rnentjes/maven")
|
url = uri("https://gitea.astraeus.nl/api/packages/rnentjes/maven")
|
||||||
}
|
}
|
||||||
maven {
|
maven {
|
||||||
url = uri("https://gitea.astraeus.nl:8443/api/packages/rnentjes/maven")
|
url = uri("https://gitea.astraeus.nl:8443/api/packages/rnentjes/maven")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvmToolchain(17)
|
jvmToolchain(21)
|
||||||
jvm()
|
jvm {
|
||||||
js {
|
@OptIn(ExperimentalKotlinGradlePluginApi::class)
|
||||||
binaries.executable()
|
binaries {
|
||||||
browser {
|
// Configures a JavaExec task named "runJvm" and a Gradle distribution for the "main" compilation in this target
|
||||||
distribution {
|
executable {
|
||||||
outputDirectory.set(File("$projectDir/web/"))
|
mainClass.set("mtmc.MainKt")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
sourceSets {
|
}
|
||||||
val commonMain by getting {
|
js {
|
||||||
dependencies {
|
binaries.executable()
|
||||||
api("nl.astraeus:kotlin-simple-logging:1.1.1")
|
browser {
|
||||||
api("nl.astraeus:kotlin-css-generator:1.0.10")
|
distribution {
|
||||||
|
outputDirectory.set(File("$projectDir/web/"))
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.0")
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
val commonTest by getting
|
|
||||||
val jvmMain by getting {
|
|
||||||
dependencies {
|
|
||||||
implementation("io.undertow:undertow-core:2.3.14.Final")
|
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:0.11.0")
|
|
||||||
|
|
||||||
implementation("org.xerial:sqlite-jdbc:3.32.3.2")
|
|
||||||
implementation("com.zaxxer:HikariCP:4.0.3")
|
|
||||||
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 {
|
|
||||||
dependencies {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val jsMain by getting {
|
|
||||||
dependencies {
|
|
||||||
implementation("nl.astraeus:kotlin-komponent:1.2.5")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val jsTest by getting
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this to enable ExperimentalStdlibApi globally
|
||||||
|
sourceSets.all {
|
||||||
|
languageSettings.optIn("kotlin.ExperimentalStdlibApi")
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
val commonMain by getting {
|
||||||
|
dependencies {
|
||||||
|
api("nl.astraeus:kotlin-simple-logging:1.1.1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val commonTest by getting
|
||||||
|
val jvmMain by getting {
|
||||||
|
dependencies {
|
||||||
|
implementation("io.undertow:undertow-core:2.3.14.Final")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:0.11.0")
|
||||||
|
|
||||||
|
implementation("org.xerial:sqlite-jdbc:3.32.3.2")
|
||||||
|
implementation("com.zaxxer:HikariCP:4.0.3")
|
||||||
|
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 {
|
||||||
|
dependencies {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val jsMain by getting {
|
||||||
|
dependencies {
|
||||||
|
implementation("nl.astraeus:kotlin-komponent:1.2.8")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val jsTest by getting
|
||||||
|
}
|
||||||
}
|
}
|
||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
|||||||
#Sun Apr 28 09:54:33 CEST 2024
|
#Sun Apr 28 09:54:33 CEST 2024
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -4,9 +4,8 @@ import mtmc.tokenizer.MTMCToken
|
|||||||
|
|
||||||
abstract class ASMElement(
|
abstract class ASMElement(
|
||||||
val labels: List<MTMCToken>,
|
val labels: List<MTMCToken>,
|
||||||
@JvmField var lineNumber: Int
|
var lineNumber: Int
|
||||||
) : HasLocation {
|
) : HasLocation {
|
||||||
@JvmField
|
|
||||||
var errors: MutableList<ASMError> = ArrayList()
|
var errors: MutableList<ASMError> = ArrayList()
|
||||||
override var location: Int = 0
|
override var location: Int = 0
|
||||||
override var sizeInBytes: Int = 0
|
override var sizeInBytes: Int = 0
|
||||||
@@ -2,10 +2,9 @@ package mtmc.asm
|
|||||||
|
|
||||||
import mtmc.tokenizer.MTMCToken
|
import mtmc.tokenizer.MTMCToken
|
||||||
|
|
||||||
@JvmRecord
|
|
||||||
data class ASMError(
|
data class ASMError(
|
||||||
@JvmField val token: MTMCToken,
|
val token: MTMCToken,
|
||||||
@JvmField val error: String
|
val error: String
|
||||||
) {
|
) {
|
||||||
fun formattedErrorMessage(): String {
|
fun formattedErrorMessage(): String {
|
||||||
return "Line " + token.line + ": " + error
|
return "Line " + token.line + ": " + error
|
||||||
@@ -25,12 +25,16 @@ import mtmc.emulator.Register.Companion.isReadable
|
|||||||
import mtmc.emulator.Register.Companion.isWriteable
|
import mtmc.emulator.Register.Companion.isWriteable
|
||||||
import mtmc.os.SysCall
|
import mtmc.os.SysCall
|
||||||
import mtmc.os.exec.Executable
|
import mtmc.os.exec.Executable
|
||||||
|
import mtmc.os.fs.File
|
||||||
import mtmc.tokenizer.MTMCToken
|
import mtmc.tokenizer.MTMCToken
|
||||||
import mtmc.tokenizer.MTMCTokenizer
|
import mtmc.tokenizer.MTMCTokenizer
|
||||||
import java.io.File
|
|
||||||
import java.nio.charset.StandardCharsets
|
fun MutableList<MTMCToken>.poll() = this.removeFirst()
|
||||||
import java.util.*
|
fun MutableList<MTMCToken>.peekFirst() = if (this.isEmpty()) {
|
||||||
import java.util.stream.Collectors
|
null
|
||||||
|
} else {
|
||||||
|
this[0]
|
||||||
|
}
|
||||||
|
|
||||||
class Assembler {
|
class Assembler {
|
||||||
var instructions: MutableList<Instruction> = ArrayList<Instruction>()
|
var instructions: MutableList<Instruction> = ArrayList<Instruction>()
|
||||||
@@ -88,7 +92,7 @@ class Assembler {
|
|||||||
|
|
||||||
var location = 0
|
var location = 0
|
||||||
var originalLineNumber = 0
|
var originalLineNumber = 0
|
||||||
val currentLocals: MutableMap<String?, LocalInfo?> = TreeMap<String?, LocalInfo?>()
|
val currentLocals: MutableMap<String?, LocalInfo?> = mutableMapOf()
|
||||||
for (instruction in instructions) {
|
for (instruction in instructions) {
|
||||||
val asmLineNumber = instruction.lineNumber
|
val asmLineNumber = instruction.lineNumber
|
||||||
if (instruction is MetaInstruction) {
|
if (instruction is MetaInstruction) {
|
||||||
@@ -145,10 +149,8 @@ class Assembler {
|
|||||||
val result = assemble(srcName, asm)
|
val result = assemble(srcName, asm)
|
||||||
if (!result.errors.isEmpty()) {
|
if (!result.errors.isEmpty()) {
|
||||||
throw RuntimeException(
|
throw RuntimeException(
|
||||||
"Errors:\n" + result.errors
|
"Errors:\n" + result.errors.joinToString("\n") { e: ASMError? -> " - " + e!!.formattedErrorMessage() }
|
||||||
.stream()
|
)
|
||||||
.map<String> { e: ASMError? -> " - " + e!!.formattedErrorMessage() }
|
|
||||||
.collect(Collectors.joining("\n")))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Executable(
|
return Executable(
|
||||||
@@ -261,7 +263,10 @@ class Assembler {
|
|||||||
for (labelToken in labelTokens) {
|
for (labelToken in labelTokens) {
|
||||||
val labelData = Data(labelTokens, labelToken.line)
|
val labelData = Data(labelTokens, labelToken.line)
|
||||||
if (hasLabel(labelToken.stringValue)) {
|
if (hasLabel(labelToken.stringValue)) {
|
||||||
labelData.addError(tokens.poll(), "Label already defined: " + labelToken.stringValue)
|
labelData.addError(
|
||||||
|
tokens.removeFirst(),
|
||||||
|
"Label already defined: " + labelToken.stringValue
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
this.labels.put(labelToken.labelValue(), labelData)
|
this.labels.put(labelToken.labelValue(), labelData)
|
||||||
}
|
}
|
||||||
@@ -274,7 +279,7 @@ class Assembler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseMetaDirective(tokens: LinkedList<MTMCToken>): Boolean {
|
private fun parseMetaDirective(tokens: MutableList<MTMCToken>): Boolean {
|
||||||
if (tokens.size >= 2 && tokens.get(0).type == MTMCToken.TokenType.AT) {
|
if (tokens.size >= 2 && tokens.get(0).type == MTMCToken.TokenType.AT) {
|
||||||
tokens.removeFirst()
|
tokens.removeFirst()
|
||||||
val metaInstruction = MetaInstruction(tokens.removeFirst())
|
val metaInstruction = MetaInstruction(tokens.removeFirst())
|
||||||
@@ -306,26 +311,26 @@ class Assembler {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseModeFlag(tokens: LinkedList<MTMCToken>): ASMMode? {
|
private fun parseModeFlag(tokens: MutableList<MTMCToken>): ASMMode? {
|
||||||
if (tokens.size >= 2 && tokens.get(0).type == MTMCToken.TokenType.DOT && tokens.get(1).type == MTMCToken.TokenType.IDENTIFIER && tokens.get(
|
if (tokens.size >= 2 && tokens.get(0).type == MTMCToken.TokenType.DOT && tokens.get(1).type == MTMCToken.TokenType.IDENTIFIER && tokens.get(
|
||||||
0
|
0
|
||||||
).end == tokens.get(1).start && (tokens.get(1).stringValue == "data" ||
|
).end == tokens.get(1).start && (tokens.get(1).stringValue == "data" ||
|
||||||
tokens.get(1).stringValue == "text")
|
tokens.get(1).stringValue == "text")
|
||||||
) {
|
) {
|
||||||
return ASMMode.valueOf(tokens.get(1).stringValue.uppercase(Locale.getDefault()))
|
return ASMMode.valueOf(tokens.get(1).stringValue.uppercase())
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseData(tokens: LinkedList<MTMCToken>, labelTokens: MutableList<MTMCToken>) {
|
private fun parseData(tokens: MutableList<MTMCToken>, labelTokens: MutableList<MTMCToken>) {
|
||||||
lastLabels = mutableListOf()
|
lastLabels = mutableListOf()
|
||||||
var dataToken = tokens.poll()
|
var dataToken = tokens.removeFirst()
|
||||||
var dataElt = Data(labelTokens, if (dataToken == null) 0 else dataToken.line)
|
var dataElt = Data(labelTokens, if (dataToken == null) 0 else dataToken.line)
|
||||||
if (dataToken != null) {
|
if (dataToken != null) {
|
||||||
if (dataToken.type == MTMCToken.TokenType.STRING) {
|
if (dataToken.type == MTMCToken.TokenType.STRING) {
|
||||||
val stringBytes = dataToken.stringValue.toByteArray(StandardCharsets.US_ASCII)
|
val stringBytes = dataToken.stringValue.encodeToByteArray()
|
||||||
val nullTerminated = ByteArray(stringBytes.size + 1)
|
val nullTerminated = ByteArray(stringBytes.size + 1)
|
||||||
System.arraycopy(stringBytes, 0, nullTerminated, 0, stringBytes.size)
|
stringBytes.copyInto(nullTerminated, 0, 0, stringBytes.size)
|
||||||
nullTerminated[stringBytes.size] = '\u0000'.code.toByte()
|
nullTerminated[stringBytes.size] = '\u0000'.code.toByte()
|
||||||
dataElt.setValue(dataToken, nullTerminated)
|
dataElt.setValue(dataToken, nullTerminated)
|
||||||
} else if (isInteger(dataToken)) {
|
} else if (isInteger(dataToken)) {
|
||||||
@@ -338,7 +343,7 @@ class Assembler {
|
|||||||
byteArrayOf((integerValue ushr 8).toByte(), integerValue.toByte())
|
byteArrayOf((integerValue ushr 8).toByte(), integerValue.toByte())
|
||||||
)
|
)
|
||||||
} else if (dataToken.type == MTMCToken.TokenType.DOT) {
|
} else if (dataToken.type == MTMCToken.TokenType.DOT) {
|
||||||
dataToken = tokens.poll()
|
dataToken = tokens.removeFirst()
|
||||||
dataElt = Data(labelTokens, dataToken.line)
|
dataElt = Data(labelTokens, dataToken.line)
|
||||||
if (dataToken.stringValue == "int") {
|
if (dataToken.stringValue == "int") {
|
||||||
val intToken = requireIntegerToken(tokens, dataElt, MonTanaMiniComputer.MEMORY_SIZE)
|
val intToken = requireIntegerToken(tokens, dataElt, MonTanaMiniComputer.MEMORY_SIZE)
|
||||||
@@ -359,7 +364,7 @@ class Assembler {
|
|||||||
dataElt.addError(dataToken, "only data types are .int, .byte, and .image")
|
dataElt.addError(dataToken, "only data types are .int, .byte, and .image")
|
||||||
}
|
}
|
||||||
} else if (dataToken.type == MTMCToken.TokenType.MINUS) {
|
} else if (dataToken.type == MTMCToken.TokenType.MINUS) {
|
||||||
val nextToken = tokens.poll() // get next
|
val nextToken = tokens.removeFirst() // get next
|
||||||
if (nextToken == null || (nextToken.type != MTMCToken.TokenType.INTEGER && nextToken.type != MTMCToken.TokenType.HEX && nextToken.type != MTMCToken.TokenType.BINARY)) {
|
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")
|
dataElt.addError(dataToken, "Number is too negative")
|
||||||
} else {
|
} else {
|
||||||
@@ -380,7 +385,7 @@ class Assembler {
|
|||||||
|
|
||||||
for (labelToken in labelTokens) {
|
for (labelToken in labelTokens) {
|
||||||
if (hasLabel(labelToken.stringValue)) {
|
if (hasLabel(labelToken.stringValue)) {
|
||||||
dataElt.addError(tokens.poll(), "Label already defined: " + labelToken.stringValue)
|
dataElt.addError(tokens.removeFirst(), "Label already defined: " + labelToken.stringValue)
|
||||||
} else {
|
} else {
|
||||||
labels.put(labelToken.labelValue(), dataElt)
|
labels.put(labelToken.labelValue(), dataElt)
|
||||||
}
|
}
|
||||||
@@ -395,7 +400,7 @@ class Assembler {
|
|||||||
): Graphic {
|
): Graphic {
|
||||||
val graphic = Graphic(labelTokens, token.line)
|
val graphic = Graphic(labelTokens, token.line)
|
||||||
val filename = token.stringValue
|
val filename = token.stringValue
|
||||||
val file = File(File(this.srcName).getParent(), filename)
|
val file = File(File(this.srcName).parent, filename)
|
||||||
val index = graphics.size
|
val index = graphics.size
|
||||||
|
|
||||||
data.setValue(token, byteArrayOf(((index shr 8) and 0xFF).toByte(), (index and 0xFF).toByte()))
|
data.setValue(token, byteArrayOf(((index shr 8) and 0xFF).toByte(), (index and 0xFF).toByte()))
|
||||||
@@ -405,9 +410,16 @@ class Assembler {
|
|||||||
return graphic
|
return graphic
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseInstruction(tokens: LinkedList<MTMCToken>, labelTokens: MutableList<MTMCToken>) {
|
private fun parseInstruction(
|
||||||
|
tokens: MutableList<MTMCToken>,
|
||||||
|
labelTokens: MutableList<MTMCToken>
|
||||||
|
) {
|
||||||
var tokens = tokens
|
var tokens = tokens
|
||||||
var instructionToken = tokens.peekFirst()
|
var instructionToken = if (tokens.isEmpty()) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
tokens[0]
|
||||||
|
}
|
||||||
if (instructionToken == null) return
|
if (instructionToken == null) return
|
||||||
|
|
||||||
lastLabels = mutableListOf()
|
lastLabels = mutableListOf()
|
||||||
@@ -510,7 +522,7 @@ class Assembler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if there is a stack register specified, consume it
|
// if there is a stack register specified, consume it
|
||||||
if (!tokens.isEmpty() && tokens.peekFirst().type == MTMCToken.TokenType.IDENTIFIER) {
|
if (!tokens.isEmpty() && tokens.peekFirst()?.type == MTMCToken.TokenType.IDENTIFIER) {
|
||||||
val stackReg = requireReadableRegister(tokens, stackInst)
|
val stackReg = requireReadableRegister(tokens, stackInst)
|
||||||
stackInst.setStackRegister(stackReg)
|
stackInst.setStackRegister(stackReg)
|
||||||
}
|
}
|
||||||
@@ -550,7 +562,7 @@ class Assembler {
|
|||||||
loadInst.setPointerToken(pointerReg)
|
loadInst.setPointerToken(pointerReg)
|
||||||
|
|
||||||
// if there is an offset register specified, consume it
|
// if there is an offset register specified, consume it
|
||||||
if (!tokens.isEmpty() && tokens.peekFirst().type == MTMCToken.TokenType.IDENTIFIER) {
|
if (!tokens.isEmpty() && tokens.peekFirst()?.type == MTMCToken.TokenType.IDENTIFIER) {
|
||||||
val offsetReg = requireReadableRegister(tokens, loadInst)
|
val offsetReg = requireReadableRegister(tokens, loadInst)
|
||||||
loadInst.setOffsetToken(offsetReg)
|
loadInst.setOffsetToken(offsetReg)
|
||||||
}
|
}
|
||||||
@@ -598,7 +610,7 @@ class Assembler {
|
|||||||
//===================================================
|
//===================================================
|
||||||
// tokenization helper functions
|
// tokenization helper functions
|
||||||
//===================================================
|
//===================================================
|
||||||
private fun requireSysCall(tokens: LinkedList<MTMCToken>, inst: Instruction): MTMCToken {
|
private fun requireSysCall(tokens: MutableList<MTMCToken>, inst: Instruction): MTMCToken {
|
||||||
val sysCallType = tokens.poll()
|
val sysCallType = tokens.poll()
|
||||||
if (sysCallType == null) {
|
if (sysCallType == null) {
|
||||||
inst.addError("Syscall required")
|
inst.addError("Syscall required")
|
||||||
@@ -610,7 +622,7 @@ class Assembler {
|
|||||||
return sysCallType!!
|
return sysCallType!!
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun requireAluOp(tokens: LinkedList<MTMCToken>, inst: Instruction): MTMCToken {
|
private fun requireAluOp(tokens: MutableList<MTMCToken>, inst: Instruction): MTMCToken {
|
||||||
val sysCallType = tokens.poll()
|
val sysCallType = tokens.poll()
|
||||||
if (sysCallType == null) {
|
if (sysCallType == null) {
|
||||||
inst.addError("Syscall required")
|
inst.addError("Syscall required")
|
||||||
@@ -623,7 +635,7 @@ class Assembler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun requireWriteableRegister(
|
private fun requireWriteableRegister(
|
||||||
tokens: LinkedList<MTMCToken>,
|
tokens: MutableList<MTMCToken>,
|
||||||
instruction: Instruction
|
instruction: Instruction
|
||||||
): MTMCToken {
|
): MTMCToken {
|
||||||
val nextToken = tokens.poll()
|
val nextToken = tokens.poll()
|
||||||
@@ -638,7 +650,7 @@ class Assembler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun requireReadableRegister(
|
private fun requireReadableRegister(
|
||||||
tokens: LinkedList<MTMCToken>,
|
tokens: MutableList<MTMCToken>,
|
||||||
instruction: Instruction
|
instruction: Instruction
|
||||||
): MTMCToken {
|
): MTMCToken {
|
||||||
val nextToken = tokens.poll()
|
val nextToken = tokens.poll()
|
||||||
@@ -652,7 +664,7 @@ class Assembler {
|
|||||||
return nextToken!!
|
return nextToken!!
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun requireALUOp(tokens: LinkedList<MTMCToken>, instruction: Instruction): MTMCToken {
|
private fun requireALUOp(tokens: MutableList<MTMCToken>, instruction: Instruction): MTMCToken {
|
||||||
val nextToken = tokens.poll()
|
val nextToken = tokens.poll()
|
||||||
if (nextToken == null || nextToken.type != MTMCToken.TokenType.IDENTIFIER || !isALUOp(nextToken.stringValue)) {
|
if (nextToken == null || nextToken.type != MTMCToken.TokenType.IDENTIFIER || !isALUOp(nextToken.stringValue)) {
|
||||||
instruction.addError("ALU operation required")
|
instruction.addError("ALU operation required")
|
||||||
@@ -660,7 +672,7 @@ class Assembler {
|
|||||||
return nextToken!!
|
return nextToken!!
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun requireString(tokens: LinkedList<MTMCToken>, instruction: ASMElement): MTMCToken {
|
private fun requireString(tokens: MutableList<MTMCToken>, instruction: ASMElement): MTMCToken {
|
||||||
val nextToken = tokens.poll()
|
val nextToken = tokens.poll()
|
||||||
if (nextToken == null || nextToken.type != MTMCToken.TokenType.STRING) {
|
if (nextToken == null || nextToken.type != MTMCToken.TokenType.STRING) {
|
||||||
instruction.addError("String required")
|
instruction.addError("String required")
|
||||||
@@ -669,7 +681,7 @@ class Assembler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun requireIntegerToken(
|
private fun requireIntegerToken(
|
||||||
tokens: LinkedList<MTMCToken>,
|
tokens: MutableList<MTMCToken>,
|
||||||
inst: ASMElement,
|
inst: ASMElement,
|
||||||
max: Int
|
max: Int
|
||||||
): MTMCToken {
|
): MTMCToken {
|
||||||
@@ -688,7 +700,7 @@ class Assembler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun requireToken(
|
private fun requireToken(
|
||||||
tokens: LinkedList<MTMCToken>,
|
tokens: MutableList<MTMCToken>,
|
||||||
type: MTMCToken.TokenType,
|
type: MTMCToken.TokenType,
|
||||||
inst: ASMElement
|
inst: ASMElement
|
||||||
): MTMCToken? {
|
): MTMCToken? {
|
||||||
@@ -700,7 +712,7 @@ class Assembler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun requireIntegerOrLabelReferenceToken(
|
private fun requireIntegerOrLabelReferenceToken(
|
||||||
tokens: LinkedList<MTMCToken>,
|
tokens: MutableList<MTMCToken>,
|
||||||
inst: LoadStoreInstruction
|
inst: LoadStoreInstruction
|
||||||
): MTMCToken {
|
): MTMCToken {
|
||||||
val token = tokens.poll()
|
val token = tokens.poll()
|
||||||
@@ -713,14 +725,14 @@ class Assembler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private val tokensForLine: LinkedList<MTMCToken>
|
private val tokensForLine: MutableList<MTMCToken>
|
||||||
get() {
|
get() {
|
||||||
val tokens = LinkedList<MTMCToken>()
|
val tokens = mutableListOf<MTMCToken>()
|
||||||
if (tokenizer!!.more()) {
|
if (tokenizer!!.more()) {
|
||||||
val first = tokenizer!!.consume()
|
val first = tokenizer!!.consume()
|
||||||
tokens.add(first)
|
tokens.add(first)
|
||||||
while (tokenizer!!.more() &&
|
while (tokenizer!!.more() &&
|
||||||
first.line == tokenizer!!.currentToken().line
|
first.line == tokenizer!!.getCurrentToken().line
|
||||||
) {
|
) {
|
||||||
tokens.add(tokenizer!!.consume())
|
tokens.add(tokenizer!!.consume())
|
||||||
}
|
}
|
||||||
@@ -742,51 +754,51 @@ class Assembler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun transformSyntheticInstructions(tokens: LinkedList<MTMCToken>): LinkedList<MTMCToken> {
|
fun transformSyntheticInstructions(tokens: MutableList<MTMCToken>): MutableList<MTMCToken> {
|
||||||
if (!tokens.isEmpty()) {
|
if (!tokens.isEmpty()) {
|
||||||
val first = tokens.peekFirst()
|
val first = tokens.first()
|
||||||
if (first.type == MTMCToken.TokenType.IDENTIFIER) {
|
if (first.type == MTMCToken.TokenType.IDENTIFIER) {
|
||||||
val stringVal = first.stringValue
|
val stringVal = first.stringValue
|
||||||
if (stringVal.endsWith("i")) {
|
if (stringVal.endsWith("i")) {
|
||||||
val op = stringVal.substring(0, stringVal.length - 1)
|
val op = stringVal.substring(0, stringVal.length - 1)
|
||||||
if (isALUOp(op)) {
|
if (isALUOp(op)) {
|
||||||
val syntheticImmediate = tokens.removeFirst()
|
val syntheticImmediate = tokens.removeFirst()
|
||||||
tokens.addFirst(syntheticImmediate.cloneWithVal(op))
|
tokens.add(0, syntheticImmediate.cloneWithVal(op))
|
||||||
tokens.addFirst(syntheticImmediate.cloneWithVal("imm"))
|
tokens.add(0, syntheticImmediate.cloneWithVal("imm"))
|
||||||
}
|
}
|
||||||
} else if (stringVal.startsWith("s")) {
|
} else if (stringVal.startsWith("s")) {
|
||||||
val op = stringVal.substring(1, stringVal.length)
|
val op = stringVal.substring(1, stringVal.length)
|
||||||
if (isALUOp(op)) {
|
if (isALUOp(op)) {
|
||||||
val syntheticImmediate = tokens.removeFirst()
|
val syntheticImmediate = tokens.removeFirst()
|
||||||
tokens.addFirst(syntheticImmediate.cloneWithVal(op))
|
tokens.add(0, syntheticImmediate.cloneWithVal(op))
|
||||||
tokens.addFirst(syntheticImmediate.cloneWithVal("sop"))
|
tokens.add(0, syntheticImmediate.cloneWithVal("sop"))
|
||||||
}
|
}
|
||||||
} else if (stringVal == "la") {
|
} else if (stringVal == "la") {
|
||||||
val syntheticImmediate = tokens.removeFirst()
|
val syntheticImmediate = tokens.removeFirst()
|
||||||
tokens.addFirst(syntheticImmediate.cloneWithVal("li"))
|
tokens.add(0, syntheticImmediate.cloneWithVal("li"))
|
||||||
} else if (stringVal == "ret") {
|
} else if (stringVal == "ret") {
|
||||||
val syntheticImmediate = tokens.removeFirst()
|
val syntheticImmediate = tokens.removeFirst()
|
||||||
tokens.addFirst(syntheticImmediate.cloneWithVal("ra"))
|
tokens.add(0, syntheticImmediate.cloneWithVal("ra"))
|
||||||
tokens.addFirst(syntheticImmediate.cloneWithVal("jr"))
|
tokens.add(0, syntheticImmediate.cloneWithVal("jr"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tokens
|
return tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun maybeGetLabels(tokens: LinkedList<MTMCToken>): MutableList<MTMCToken> {
|
private fun maybeGetLabels(tokens: MutableList<MTMCToken>): MutableList<MTMCToken> {
|
||||||
val labels = LinkedList<MTMCToken>()
|
val labels = mutableListOf<MTMCToken>()
|
||||||
while (!tokens.isEmpty() && tokens.getFirst().type == MTMCToken.TokenType.LABEL) {
|
while (!tokens.isEmpty() && tokens.first().type == MTMCToken.TokenType.LABEL) {
|
||||||
val label = tokens.poll()
|
val label = tokens.removeFirst()
|
||||||
labels.add(label!!)
|
labels.add(label!!)
|
||||||
}
|
}
|
||||||
return labels
|
return labels
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun maybeGetLabelReference(tokens: LinkedList<MTMCToken>): MTMCToken? {
|
private fun maybeGetLabelReference(tokens: MutableList<MTMCToken>): MTMCToken? {
|
||||||
var label: MTMCToken? = null
|
var label: MTMCToken? = null
|
||||||
if (tokens.getFirst().type == MTMCToken.TokenType.IDENTIFIER) {
|
if (tokens.first().type == MTMCToken.TokenType.IDENTIFIER) {
|
||||||
label = tokens.poll()
|
label = tokens.removeFirst()
|
||||||
}
|
}
|
||||||
return label
|
return label
|
||||||
}
|
}
|
||||||
@@ -2,13 +2,12 @@ package mtmc.asm
|
|||||||
|
|
||||||
import mtmc.emulator.DebugInfo
|
import mtmc.emulator.DebugInfo
|
||||||
|
|
||||||
@JvmRecord
|
|
||||||
data class AssemblyResult(
|
data class AssemblyResult(
|
||||||
@JvmField val code: ByteArray,
|
val code: ByteArray,
|
||||||
val data: ByteArray,
|
val data: ByteArray,
|
||||||
val graphics: Array<ByteArray>,
|
val graphics: Array<ByteArray>,
|
||||||
@JvmField val debugInfo: DebugInfo?,
|
val debugInfo: DebugInfo?,
|
||||||
@JvmField val errors: MutableList<ASMError>
|
val errors: MutableList<ASMError>
|
||||||
) {
|
) {
|
||||||
fun printErrors(): String {
|
fun printErrors(): String {
|
||||||
val builder = StringBuilder("Errors:\n")
|
val builder = StringBuilder("Errors:\n")
|
||||||
@@ -34,4 +34,4 @@ class Data(labels: MutableList<MTMCToken>, lineNumber: Int) : ASMElement(labels,
|
|||||||
override fun addError(err: String) {
|
override fun addError(err: String) {
|
||||||
addError(labels.last(), err)
|
addError(labels.last(), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
45
src/commonMain/kotlin/mtmc/asm/graphics/Graphic.kt
Normal file
45
src/commonMain/kotlin/mtmc/asm/graphics/Graphic.kt
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package mtmc.asm.graphics
|
||||||
|
|
||||||
|
import mtmc.asm.ASMElement
|
||||||
|
import mtmc.tokenizer.MTMCToken
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author jbanes
|
||||||
|
*/
|
||||||
|
class Graphic(labels: MutableList<MTMCToken>, 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) {}
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@ import mtmc.emulator.Register.Companion.fromInteger
|
|||||||
import mtmc.emulator.Register.Companion.toInteger
|
import mtmc.emulator.Register.Companion.toInteger
|
||||||
import mtmc.tokenizer.MTMCToken
|
import mtmc.tokenizer.MTMCToken
|
||||||
import mtmc.util.BinaryUtils
|
import mtmc.util.BinaryUtils
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
class ALUInstruction(
|
class ALUInstruction(
|
||||||
type: InstructionType,
|
type: InstructionType,
|
||||||
@@ -38,7 +37,7 @@ class ALUInstruction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val isBinaryOp: Boolean
|
val isBinaryOp: Boolean
|
||||||
get() = !ALUOp.valueOf(instructionToken.stringValue.uppercase(Locale.getDefault())).isUnary
|
get() = !ALUOp.valueOf(instructionToken.stringValue.uppercase()).isUnary
|
||||||
|
|
||||||
override fun genCode(output: ByteArray, assembler: Assembler) {
|
override fun genCode(output: ByteArray, assembler: Assembler) {
|
||||||
val opCode = ALUOp.toInteger(instructionToken.stringValue)
|
val opCode = ALUOp.toInteger(instructionToken.stringValue)
|
||||||
@@ -62,13 +61,12 @@ class ALUInstruction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
|
||||||
fun disassemble(instruction: Short): String? {
|
fun disassemble(instruction: Short): String? {
|
||||||
if (BinaryUtils.getBits(16, 4, instruction).toInt() == 1) {
|
if (BinaryUtils.getBits(16, 4, instruction).toInt() == 1) {
|
||||||
val builder = StringBuilder()
|
val builder = StringBuilder()
|
||||||
val opCode = BinaryUtils.getBits(12, 4, instruction)
|
val opCode = BinaryUtils.getBits(12, 4, instruction)
|
||||||
val op = ALUOp.fromInt(opCode)
|
val op = ALUOp.fromInt(opCode)
|
||||||
val aluOp = ALUOp.valueOf(op.uppercase(Locale.getDefault()))
|
val aluOp = ALUOp.valueOf(op.uppercase())
|
||||||
if (aluOp == ALUOp.IMM) {
|
if (aluOp == ALUOp.IMM) {
|
||||||
builder.append(op).append(" ")
|
builder.append(op).append(" ")
|
||||||
builder.append(fromInteger(BinaryUtils.getBits(8, 4, instruction).toInt())).append(" ")
|
builder.append(fromInteger(BinaryUtils.getBits(8, 4, instruction).toInt())).append(" ")
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
package mtmc.asm.instructions
|
package mtmc.asm.instructions
|
||||||
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
enum class ALUOp(val opCode: Int, val isUnary: Boolean) {
|
enum class ALUOp(val opCode: Int, val isUnary: Boolean) {
|
||||||
ADD(0x0000, false),
|
ADD(0x0000, false),
|
||||||
SUB(0x0001, false),
|
SUB(0x0001, false),
|
||||||
@@ -21,20 +19,17 @@ enum class ALUOp(val opCode: Int, val isUnary: Boolean) {
|
|||||||
IMM(0x000F, true);
|
IMM(0x000F, true);
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
|
||||||
fun toInteger(instruction: String): Int {
|
fun toInteger(instruction: String): Int {
|
||||||
return valueOf(instruction.uppercase(Locale.getDefault())).opCode
|
return valueOf(instruction.uppercase()).opCode
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun fromInt(opCode: Short): String {
|
fun fromInt(opCode: Short): String {
|
||||||
return entries[opCode.toInt()].name.lowercase(Locale.getDefault())
|
return entries[opCode.toInt()].name.lowercase()
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun isALUOp(op: String): Boolean {
|
fun isALUOp(op: String): Boolean {
|
||||||
try {
|
try {
|
||||||
val aluOp = valueOf(op.uppercase(Locale.getDefault()))
|
val aluOp = valueOf(op.uppercase())
|
||||||
return true
|
return true
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
return false
|
return false
|
||||||
@@ -7,9 +7,9 @@ import mtmc.emulator.MonTanaMiniComputer.Companion.isDoubleWordInstruction
|
|||||||
import mtmc.tokenizer.MTMCToken
|
import mtmc.tokenizer.MTMCToken
|
||||||
|
|
||||||
abstract class Instruction(
|
abstract class Instruction(
|
||||||
@JvmField val type: InstructionType,
|
val type: InstructionType,
|
||||||
labels: List<MTMCToken>,
|
labels: List<MTMCToken>,
|
||||||
@JvmField val instructionToken: MTMCToken
|
val instructionToken: MTMCToken
|
||||||
) : ASMElement(labels, instructionToken.line) {
|
) : ASMElement(labels, instructionToken.line) {
|
||||||
open fun validateLabel(assembler: Assembler) {
|
open fun validateLabel(assembler: Assembler) {
|
||||||
// default does nothing
|
// default does nothing
|
||||||
@@ -25,7 +25,6 @@ abstract class Instruction(
|
|||||||
get() = type.sizeInBytes
|
get() = type.sizeInBytes
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
|
||||||
fun isInstruction(cmd: String): Boolean {
|
fun isInstruction(cmd: String): Boolean {
|
||||||
return InstructionType.fromString(cmd) != null
|
return InstructionType.fromString(cmd) != null
|
||||||
}
|
}
|
||||||
@@ -58,7 +57,7 @@ abstract class Instruction(
|
|||||||
if (ls != null) {
|
if (ls != null) {
|
||||||
return ls
|
return ls
|
||||||
}
|
}
|
||||||
val jumpReg = JumpInstruction.disassemble(instruction)
|
val jumpReg = JumpRegisterInstruction.disassemble(instruction)
|
||||||
if (jumpReg != null) {
|
if (jumpReg != null) {
|
||||||
return jumpReg
|
return jumpReg
|
||||||
}
|
}
|
||||||
@@ -66,7 +65,7 @@ abstract class Instruction(
|
|||||||
if (jump != null) {
|
if (jump != null) {
|
||||||
return jump
|
return jump
|
||||||
}
|
}
|
||||||
return "<unknown>"
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
package mtmc.asm.instructions
|
package mtmc.asm.instructions
|
||||||
|
|
||||||
import java.util.*
|
enum class InstructionType constructor(
|
||||||
|
val instructionClass: InstructionClass?,
|
||||||
enum class InstructionType @JvmOverloads constructor(
|
|
||||||
@JvmField val instructionClass: InstructionClass?,
|
|
||||||
val sizeInBytes: Int = 2
|
val sizeInBytes: Int = 2
|
||||||
) {
|
) {
|
||||||
SYS(InstructionClass.MISC),
|
SYS(InstructionClass.MISC),
|
||||||
@@ -86,10 +84,9 @@ enum class InstructionType @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
|
||||||
fun fromString(string: String): InstructionType? {
|
fun fromString(string: String): InstructionType? {
|
||||||
try {
|
try {
|
||||||
return valueOf(string.uppercase(Locale.getDefault()))
|
return valueOf(string.uppercase())
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -26,10 +26,16 @@ class JumpRegisterInstruction(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun disassemble(instruction: Short): String? {
|
fun disassemble(instruction: Short): String? {
|
||||||
if (BinaryUtils.getBits(16, 5, instruction).toInt() == 9) {
|
if (BinaryUtils.getBits(16, 4, instruction).toInt() == 9) {
|
||||||
val reg = BinaryUtils.getBits(4, 4, instruction)
|
val reg = BinaryUtils.getBits(4, 4, instruction)
|
||||||
val sb = StringBuilder("jr")
|
if (reg.toInt() == 11) {
|
||||||
sb.append(fromInteger(reg.toInt()))
|
return "ret"
|
||||||
|
} else {
|
||||||
|
val sb = StringBuilder("jr")
|
||||||
|
sb.append(" ")
|
||||||
|
sb.append(fromInteger(reg.toInt()))
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
20
src/commonMain/kotlin/mtmc/emulator/BufferedImage.kt
Normal file
20
src/commonMain/kotlin/mtmc/emulator/BufferedImage.kt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package mtmc.emulator
|
||||||
|
|
||||||
|
expect fun createBufferedImage(width: Int, height: Int): BufferedImage
|
||||||
|
|
||||||
|
expect fun createCanvasImage(width: Int, height: Int): BufferedImage
|
||||||
|
|
||||||
|
expect fun createGLImage(width: Int, height: Int): BufferedImage
|
||||||
|
|
||||||
|
interface BufferedImage {
|
||||||
|
val width: Int
|
||||||
|
val height: Int
|
||||||
|
|
||||||
|
fun getRGB(x: Int, y: Int): Int
|
||||||
|
fun setRGB(x: Int, y: Int, intVal: Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Dimension(
|
||||||
|
val width: Int,
|
||||||
|
val height: Int
|
||||||
|
)
|
||||||
8
src/commonMain/kotlin/mtmc/emulator/Color.kt
Normal file
8
src/commonMain/kotlin/mtmc/emulator/Color.kt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package mtmc.emulator
|
||||||
|
|
||||||
|
class Color(
|
||||||
|
val r: Int,
|
||||||
|
val g: Int,
|
||||||
|
val b: Int,
|
||||||
|
) {
|
||||||
|
}
|
||||||
47
src/commonMain/kotlin/mtmc/emulator/DebugInfo.kt
Normal file
47
src/commonMain/kotlin/mtmc/emulator/DebugInfo.kt
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package mtmc.emulator
|
||||||
|
|
||||||
|
import nl.astraeus.logger.getTimestamp
|
||||||
|
|
||||||
|
data class DebugInfo(
|
||||||
|
val debugStrings: MutableList<String>,
|
||||||
|
val assemblyFile: String?,
|
||||||
|
val assemblySource: String,
|
||||||
|
val assemblyLineNumbers: IntArray,
|
||||||
|
val originalFile: String,
|
||||||
|
val originalLineNumbers: IntArray,
|
||||||
|
val globals: Array<GlobalInfo>,
|
||||||
|
val locals: Array<Array<LocalInfo?>>
|
||||||
|
) {
|
||||||
|
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())
|
||||||
|
formattedString.append(monTanaMiniComputer.getRegisterValue(register).toInt())
|
||||||
|
start = matcher.end()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
formattedString.append(match)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
formattedString.append(debugString.substring(start))
|
||||||
|
println("DEBUG[" + getTimestamp() + "] : " + formattedString)
|
||||||
|
*/
|
||||||
|
println("DEBUG[" + getTimestamp() + "] : " + debugString)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class GlobalInfo(val name: String?, val location: Int, val type: String?)
|
||||||
|
|
||||||
|
data class LocalInfo(val name: String?, val offset: Int, val type: String?)
|
||||||
|
}
|
||||||
94
src/commonMain/kotlin/mtmc/emulator/MTMCClock.kt
Normal file
94
src/commonMain/kotlin/mtmc/emulator/MTMCClock.kt
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
package mtmc.emulator
|
||||||
|
|
||||||
|
import mtmc.emulator.MonTanaMiniComputer.ComputerStatus
|
||||||
|
import mtmc.util.currentTimeMillis
|
||||||
|
import mtmc.util.requestAnimationFrame
|
||||||
|
import mtmc.util.setTimeout
|
||||||
|
import mtmc.util.time
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author jbanes
|
||||||
|
*/
|
||||||
|
class MTMCClock(
|
||||||
|
private val computer: MonTanaMiniComputer
|
||||||
|
) {
|
||||||
|
var instructions: Long = 0
|
||||||
|
var ips: Long = 0
|
||||||
|
var expected: Long = 0
|
||||||
|
var virtual: Long = 0
|
||||||
|
|
||||||
|
var startTime = currentTimeMillis()
|
||||||
|
var lastFrame = 0.0
|
||||||
|
|
||||||
|
var speed: Long = 0
|
||||||
|
|
||||||
|
var instructionsToRun = 0.0
|
||||||
|
var frame = 0
|
||||||
|
|
||||||
|
fun run() {
|
||||||
|
requestAnimationFrame { handleFrame() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleFrame() {
|
||||||
|
// figure out how many instructions to execute this 'time' duration
|
||||||
|
// maximize time so we don't hang if the emulator is too slow
|
||||||
|
val time = time()
|
||||||
|
if (lastFrame == 0.0) {
|
||||||
|
lastFrame = time
|
||||||
|
requestAnimationFrame { handleFrame() }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val delta = time - lastFrame
|
||||||
|
lastFrame = time
|
||||||
|
|
||||||
|
val timeToProcess = min(delta, 16.0)
|
||||||
|
|
||||||
|
// assume 1Hz = 1 instruction/second
|
||||||
|
if (computer.getStatus() == ComputerStatus.EXECUTING) {
|
||||||
|
speed = max(computer.speed, 0).toLong()
|
||||||
|
if (speed == 0L) {
|
||||||
|
speed = 1L
|
||||||
|
}
|
||||||
|
|
||||||
|
instructionsToRun += timeToProcess * speed / 1000.0
|
||||||
|
val pulse: Long = instructionsToRun.toLong() + 1
|
||||||
|
instructionsToRun -= pulse
|
||||||
|
|
||||||
|
val start = time()
|
||||||
|
val ir = computer.pulse(pulse)
|
||||||
|
instructions += ir
|
||||||
|
val duration = (time() - start)
|
||||||
|
val actual = ir / (delta / 1000.0)
|
||||||
|
if (frame % 100 == 0) {
|
||||||
|
println("Instructions ran: $ir (delta = ${delta.toFloat()}, timeToProcess = ${timeToProcess.toFloat()}, speed = $speed (actual=${actual.toLong()}), duration = $duration)")
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual += instructions
|
||||||
|
ips = (instructions / timeToProcess).toLong()
|
||||||
|
frame++
|
||||||
|
|
||||||
|
if (duration > timeToProcess) {
|
||||||
|
setTimeout({ handleFrame() })
|
||||||
|
} else {
|
||||||
|
requestAnimationFrame { handleFrame() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
package mtmc.emulator
|
package mtmc.emulator
|
||||||
|
|
||||||
|
import mtmc.os.fs.Console
|
||||||
import mtmc.os.shell.Shell
|
import mtmc.os.shell.Shell
|
||||||
import mtmc.tokenizer.MTMCScanner
|
import mtmc.tokenizer.MTMCScanner
|
||||||
import mtmc.tokenizer.MTMCToken
|
import mtmc.tokenizer.MTMCToken
|
||||||
import java.io.Console
|
|
||||||
|
|
||||||
class MTMCConsole(private val computer: MonTanaMiniComputer) {
|
class MTMCConsole(private val computer: MonTanaMiniComputer) {
|
||||||
var mode: Mode = Mode.NON_INTERACTIVE
|
var mode: Mode = Mode.INTERACTIVE
|
||||||
var sysConsole: Console? = null
|
var sysConsole: Console? = null
|
||||||
|
|
||||||
// non-interactive data
|
// non-interactive data
|
||||||
private val output = StringBuffer()
|
private var output = StringBuilder()
|
||||||
private var shortValueSet = false
|
private var shortValueSet = false
|
||||||
private var shortValue: Short = 0
|
private var shortValue: Short = 0
|
||||||
private var stringValue: String? = null
|
private var stringValue: String? = null
|
||||||
@@ -18,12 +18,7 @@ class MTMCConsole(private val computer: MonTanaMiniComputer) {
|
|||||||
// TODO invert so shell is driving and console is just IO
|
// TODO invert so shell is driving and console is just IO
|
||||||
fun start() {
|
fun start() {
|
||||||
mode = Mode.INTERACTIVE
|
mode = Mode.INTERACTIVE
|
||||||
sysConsole = System.console()
|
|
||||||
Shell.printShellWelcome(computer)
|
Shell.printShellWelcome(computer)
|
||||||
while (true) {
|
|
||||||
val cmd = sysConsole!!.readLine("mtmc > ")
|
|
||||||
computer.oS.processCommand(cmd)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun println(x: String) {
|
fun println(x: String) {
|
||||||
@@ -48,7 +43,7 @@ class MTMCConsole(private val computer: MonTanaMiniComputer) {
|
|||||||
if (mode == Mode.INTERACTIVE) {
|
if (mode == Mode.INTERACTIVE) {
|
||||||
val tokens = MTMCScanner(sysConsole!!.readLine(), "#").tokenize()
|
val tokens = MTMCScanner(sysConsole!!.readLine(), "#").tokenize()
|
||||||
val token = tokens.first()
|
val token = tokens.first()
|
||||||
assert(token.type === MTMCToken.TokenType.CHAR)
|
check(token.type === MTMCToken.TokenType.CHAR)
|
||||||
return token.charValue()
|
return token.charValue()
|
||||||
} else {
|
} else {
|
||||||
this.shortValueSet = false
|
this.shortValueSet = false
|
||||||
@@ -88,7 +83,9 @@ class MTMCConsole(private val computer: MonTanaMiniComputer) {
|
|||||||
val text = if (index >= 0) output.substring(0, index + 1) else ""
|
val text = if (index >= 0) output.substring(0, index + 1) else ""
|
||||||
|
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
output.delete(0, index + 1)
|
val updated = StringBuilder()
|
||||||
|
updated.append(output.removeRange(0, index + 1))
|
||||||
|
output = updated
|
||||||
}
|
}
|
||||||
|
|
||||||
return text
|
return text
|
||||||
@@ -121,7 +118,7 @@ class MTMCConsole(private val computer: MonTanaMiniComputer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun resetOutput() {
|
fun resetOutput() {
|
||||||
output.delete(0, output.length)
|
output.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Mode {
|
enum class Mode {
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,7 +1,5 @@
|
|||||||
package mtmc.emulator
|
package mtmc.emulator
|
||||||
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class MTMCIO {
|
class MTMCIO {
|
||||||
var value: Int = 0
|
var value: Int = 0
|
||||||
|
|
||||||
@@ -17,12 +15,12 @@ class MTMCIO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun keyPressed(key: String) {
|
fun keyPressed(key: String) {
|
||||||
val button = Buttons.valueOf(key.uppercase(Locale.getDefault()))
|
val button = Buttons.valueOf(key.uppercase())
|
||||||
value = value or button.mask
|
value = value or button.mask
|
||||||
}
|
}
|
||||||
|
|
||||||
fun keyReleased(key: String) {
|
fun keyReleased(key: String) {
|
||||||
val button = Buttons.valueOf(key.uppercase(Locale.getDefault()))
|
val button = Buttons.valueOf(key.uppercase())
|
||||||
value = value and button.mask.inv()
|
value = value and button.mask.inv()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,19 +4,7 @@ import mtmc.asm.instructions.Instruction
|
|||||||
import mtmc.os.MTOS
|
import mtmc.os.MTOS
|
||||||
import mtmc.os.fs.FileSystem
|
import mtmc.os.fs.FileSystem
|
||||||
import mtmc.util.BinaryUtils.getBits
|
import mtmc.util.BinaryUtils.getBits
|
||||||
import java.lang.Byte
|
import mtmc.util.Runnable
|
||||||
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.and
|
||||||
import kotlin.experimental.inv
|
import kotlin.experimental.inv
|
||||||
import kotlin.experimental.or
|
import kotlin.experimental.or
|
||||||
@@ -30,7 +18,7 @@ class MonTanaMiniComputer {
|
|||||||
var memory: ByteArray = ByteArray(MEMORY_SIZE)
|
var memory: ByteArray = ByteArray(MEMORY_SIZE)
|
||||||
var breakpoints: ByteArray = ByteArray(MEMORY_SIZE)
|
var breakpoints: ByteArray = ByteArray(MEMORY_SIZE)
|
||||||
private var status: ComputerStatus? = ComputerStatus.READY
|
private var status: ComputerStatus? = ComputerStatus.READY
|
||||||
var speed: Int = 1000000
|
var speed: Int = 1
|
||||||
set(speed) {
|
set(speed) {
|
||||||
field = speed
|
field = speed
|
||||||
this.notifyOfExecutionUpdate()
|
this.notifyOfExecutionUpdate()
|
||||||
@@ -43,10 +31,12 @@ class MonTanaMiniComputer {
|
|||||||
var display: MTMCDisplay = MTMCDisplay(this)
|
var display: MTMCDisplay = MTMCDisplay(this)
|
||||||
var clock: MTMCClock = MTMCClock(this)
|
var clock: MTMCClock = MTMCClock(this)
|
||||||
var fileSystem: FileSystem = FileSystem(this)
|
var fileSystem: FileSystem = FileSystem(this)
|
||||||
var rewindSteps: LinkedList<RewindStep>? = LinkedList<RewindStep>()
|
|
||||||
|
var rewindSteps = Array(MAX_REWIND_STEPS) { RewindStep() }
|
||||||
|
var rewindIndex = -1
|
||||||
|
|
||||||
// listeners
|
// listeners
|
||||||
private val observers: MutableList<MTMCObserver> = ArrayList<MTMCObserver>()
|
private val observers = mutableListOf<MTMCObserver>()
|
||||||
var debugInfo: DebugInfo? = null
|
var debugInfo: DebugInfo? = null
|
||||||
private var currentRewindStep: RewindStep? = null
|
private var currentRewindStep: RewindStep? = null
|
||||||
|
|
||||||
@@ -58,32 +48,33 @@ class MonTanaMiniComputer {
|
|||||||
registerFile = ShortArray(Register.entries.size)
|
registerFile = ShortArray(Register.entries.size)
|
||||||
memory = ByteArray(MEMORY_SIZE)
|
memory = ByteArray(MEMORY_SIZE)
|
||||||
breakpoints = ByteArray(MEMORY_SIZE)
|
breakpoints = ByteArray(MEMORY_SIZE)
|
||||||
rewindSteps = null
|
|
||||||
setRegisterValue(
|
setRegisterValue(
|
||||||
Register.SP,
|
Register.SP,
|
||||||
MEMORY_SIZE.toShort().toInt()
|
MEMORY_SIZE.toShort().toInt()
|
||||||
) // default the stack pointer to the top of memory
|
) // default the stack pointer to the top of memory
|
||||||
rewindSteps = LinkedList<RewindStep>()
|
rewindIndex = -1
|
||||||
observers!!.forEach(Consumer { obj: MTMCObserver? -> obj!!.computerReset() })
|
observers.forEach { obj ->
|
||||||
|
obj.computerReset()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun load(code: ByteArray, data: ByteArray, debugInfo: DebugInfo?) {
|
fun load(
|
||||||
load(code, data, Array(0) { ByteArray(0) }, debugInfo)
|
code: ByteArray,
|
||||||
}
|
data: ByteArray,
|
||||||
|
graphics: Array<ByteArray> = arrayOf(),
|
||||||
fun load(code: ByteArray, data: ByteArray, graphics: Array<ByteArray>, debugInfo: DebugInfo?) {
|
debugInfo: DebugInfo? = null
|
||||||
|
) {
|
||||||
this.debugInfo = debugInfo
|
this.debugInfo = debugInfo
|
||||||
|
|
||||||
// reset memory
|
// reset memory
|
||||||
initMemory()
|
initMemory()
|
||||||
|
|
||||||
|
|
||||||
val codeBoundary = code.size
|
val codeBoundary = code.size
|
||||||
System.arraycopy(code, 0, memory, 0, codeBoundary)
|
code.copyInto(memory, 0, 0, codeBoundary)
|
||||||
setRegisterValue(Register.CB, codeBoundary - 1)
|
setRegisterValue(Register.CB, codeBoundary - 1)
|
||||||
|
|
||||||
val dataBoundary = codeBoundary + data.size
|
val dataBoundary = codeBoundary + data.size
|
||||||
System.arraycopy(data, 0, memory, codeBoundary, data.size)
|
data.copyInto(memory, codeBoundary, 0, data.size)
|
||||||
setRegisterValue(Register.DB, dataBoundary - 1)
|
setRegisterValue(Register.DB, dataBoundary - 1)
|
||||||
|
|
||||||
|
|
||||||
@@ -142,8 +133,9 @@ class MonTanaMiniComputer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun fetchAndExecute() {
|
fun fetchAndExecute() {
|
||||||
currentRewindStep = RewindStep()
|
rewindIndex = (rewindIndex + 1) % rewindSteps.size
|
||||||
rewindSteps!!.push(currentRewindStep)
|
rewindSteps[rewindIndex].index = 0
|
||||||
|
|
||||||
fetchCurrentInstruction()
|
fetchCurrentInstruction()
|
||||||
val instruction = getRegisterValue(Register.IR)
|
val instruction = getRegisterValue(Register.IR)
|
||||||
if (isDoubleWordInstruction(instruction)) {
|
if (isDoubleWordInstruction(instruction)) {
|
||||||
@@ -158,7 +150,7 @@ class MonTanaMiniComputer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun execInstruction(instruction: Short) {
|
fun execInstruction(instruction: Short) {
|
||||||
observers!!.forEach(Consumer { o: MTMCObserver? -> o!!.beforeExecution(instruction) })
|
observers!!.forEach({ o: MTMCObserver? -> o!!.beforeExecution(instruction) })
|
||||||
val instructionType: Short = getBits(16, 4, instruction)
|
val instructionType: Short = getBits(16, 4, instruction)
|
||||||
if (instructionType.toInt() == 0x0000) { // MISC
|
if (instructionType.toInt() == 0x0000) { // MISC
|
||||||
val topNibble: Int = getBits(12, 4, instruction).toInt()
|
val topNibble: Int = getBits(12, 4, instruction).toInt()
|
||||||
@@ -710,7 +702,9 @@ class MonTanaMiniComputer {
|
|||||||
} else {
|
} else {
|
||||||
badInstruction(instruction)
|
badInstruction(instruction)
|
||||||
}
|
}
|
||||||
observers.forEach(Consumer { o: MTMCObserver? -> o!!.afterExecution(instruction) })
|
observers.forEach { o: MTMCObserver? ->
|
||||||
|
o!!.afterExecution(instruction)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val isFlagTestBitSet: Boolean
|
val isFlagTestBitSet: Boolean
|
||||||
@@ -733,7 +727,11 @@ class MonTanaMiniComputer {
|
|||||||
private fun badInstruction(instruction: Short) {
|
private fun badInstruction(instruction: Short) {
|
||||||
setStatus(ComputerStatus.PERMANENT_ERROR)
|
setStatus(ComputerStatus.PERMANENT_ERROR)
|
||||||
// TODO implement flags
|
// TODO implement flags
|
||||||
console.println("BAD INSTRUCTION: 0x" + Integer.toHexString(instruction.toInt() and 0xFFFF))
|
console.println(
|
||||||
|
"BAD INSTRUCTION: 0x" + (instruction.toHexString()) + " at PC " + getRegisterValue(
|
||||||
|
Register.PC
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fetchCurrentInstruction() {
|
fun fetchCurrentInstruction() {
|
||||||
@@ -746,14 +744,16 @@ class MonTanaMiniComputer {
|
|||||||
} else {
|
} else {
|
||||||
setRegisterValue(Register.DR, 0)
|
setRegisterValue(Register.DR, 0)
|
||||||
}
|
}
|
||||||
observers!!.forEach(Consumer { o: MTMCObserver? -> o!!.instructionFetched(instruction) })
|
observers.forEach { o: MTMCObserver? ->
|
||||||
|
o!!.instructionFetched(instruction)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fetchWordFromMemory(address: Int): Short {
|
fun fetchWordFromMemory(address: Int): Short {
|
||||||
val upperByte = fetchByteFromMemory(address).toShort()
|
val upperByte = fetchByteFromMemory(address).toShort()
|
||||||
val lowerByte = fetchByteFromMemory(address + 1)
|
val lowerByte = fetchByteFromMemory(address + 1)
|
||||||
var value = (upperByte.toInt() shl 8).toShort()
|
var value = (upperByte.toInt() shl 8).toShort()
|
||||||
val i = value.toInt() or Byte.toUnsignedInt(lowerByte)
|
val i = value.toInt() + (lowerByte.toInt() and 255)
|
||||||
value = i.toShort()
|
value = i.toShort()
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
@@ -762,9 +762,8 @@ class MonTanaMiniComputer {
|
|||||||
if (address < 0 || address >= memory.size) {
|
if (address < 0 || address >= memory.size) {
|
||||||
setStatus(ComputerStatus.PERMANENT_ERROR)
|
setStatus(ComputerStatus.PERMANENT_ERROR)
|
||||||
console.println(
|
console.println(
|
||||||
"BAD MEMORY LOCATION ON READ: " + address + " (0x" + Integer.toHexString(
|
"BAD MEMORY LOCATION ON READ: $address (0x" +
|
||||||
address and 0xFFFF
|
(address and 0xFFFF).toHexString() + ")"
|
||||||
) + ")"
|
|
||||||
)
|
)
|
||||||
return 0
|
return 0
|
||||||
} else {
|
} else {
|
||||||
@@ -782,24 +781,22 @@ class MonTanaMiniComputer {
|
|||||||
if (address < 0 || address >= memory.size) {
|
if (address < 0 || address >= memory.size) {
|
||||||
setStatus(ComputerStatus.PERMANENT_ERROR)
|
setStatus(ComputerStatus.PERMANENT_ERROR)
|
||||||
console.println(
|
console.println(
|
||||||
"BAD MEMORY LOCATION ON WRITE: " + address + " (0x" + Integer.toHexString(
|
"BAD MEMORY LOCATION ON WRITE: " + address + " (0x" +
|
||||||
address and 0xFFFF
|
(address and 0xFFFF).toHexString() + ")"
|
||||||
) + ")"
|
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val currentValue = memory[address]
|
val currentValue = memory[address]
|
||||||
addRewindStep(Runnable { memory[address] = currentValue })
|
addRewindStep { memory[address] = currentValue }
|
||||||
memory[address] = value
|
memory[address] = value
|
||||||
observers!!.forEach(Consumer { o: MTMCObserver? -> o!!.memoryUpdated(address, value) })
|
observers.forEach { o: MTMCObserver? ->
|
||||||
|
o!!.memoryUpdated(address, value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addRewindStep(runnable: Runnable?) {
|
private fun addRewindStep(runnable: Runnable?) {
|
||||||
if (currentRewindStep != null && rewindSteps != null) {
|
if (currentRewindStep != null) {
|
||||||
currentRewindStep!!.addSubStep(runnable)
|
currentRewindStep!!.addSubStep(runnable)
|
||||||
if (rewindSteps!!.size > MAX_REWIND_STEPS) {
|
|
||||||
rewindSteps!!.removeLast()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -812,11 +809,13 @@ class MonTanaMiniComputer {
|
|||||||
// TODO mark as overflow
|
// TODO mark as overflow
|
||||||
}
|
}
|
||||||
val currentValue = registerFile[register]
|
val currentValue = registerFile[register]
|
||||||
addRewindStep(Runnable {
|
addRewindStep {
|
||||||
registerFile[register] = currentValue
|
registerFile[register] = currentValue
|
||||||
})
|
}
|
||||||
registerFile[register] = value.toShort()
|
registerFile[register] = value.toShort()
|
||||||
observers!!.forEach(Consumer { o: MTMCObserver? -> o!!.registerUpdated(register, value) })
|
observers.forEach { o: MTMCObserver? ->
|
||||||
|
o!!.registerUpdated(register, value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRegisterValue(register: Register): Short {
|
fun getRegisterValue(register: Register): Short {
|
||||||
@@ -845,12 +844,14 @@ class MonTanaMiniComputer {
|
|||||||
breakpoints[address] = (if (active) 1.toByte() else 0.toByte())
|
breakpoints[address] = (if (active) 1.toByte() else 0.toByte())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun start() {
|
fun start() {
|
||||||
console.start() // start the interactive console
|
console.start() // start the interactive console
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getBytesFromMemory(address: Int, length: Int): ByteArray {
|
fun getBytesFromMemory(address: Int, length: Int): ByteArray {
|
||||||
return Arrays.copyOfRange(memory, address, address + length)
|
val result = ByteArray(length)
|
||||||
|
memory.copyInto(result, 0, address, address + length)
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
val oS: MTOS
|
val oS: MTOS
|
||||||
@@ -858,18 +859,15 @@ class MonTanaMiniComputer {
|
|||||||
|
|
||||||
val memoryAddresses: Iterable<Int?>
|
val memoryAddresses: Iterable<Int?>
|
||||||
get() = Iterable {
|
get() = Iterable {
|
||||||
IntStream.range(
|
(0..MEMORY_SIZE).iterator()
|
||||||
0,
|
|
||||||
MEMORY_SIZE
|
|
||||||
).iterator()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addObserver(observer: MTMCObserver?) {
|
fun addObserver(observer: MTMCObserver?) {
|
||||||
observers!!.add(observer!!)
|
observers.add(observer!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeObserver(observer: MTMCObserver?) {
|
fun removeObserver(observer: MTMCObserver?) {
|
||||||
observers!!.remove(observer!!)
|
observers.remove(observer!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pause() {
|
fun pause() {
|
||||||
@@ -936,7 +934,7 @@ class MonTanaMiniComputer {
|
|||||||
fun setArg(arg: String) {
|
fun setArg(arg: String) {
|
||||||
if (!arg.isEmpty()) {
|
if (!arg.isEmpty()) {
|
||||||
val start = getRegisterValue(Register.BP)
|
val start = getRegisterValue(Register.BP)
|
||||||
val bytes = arg.toByteArray()
|
val bytes = arg.encodeToByteArray()
|
||||||
writeStringToMemory(start.toInt(), bytes)
|
writeStringToMemory(start.toInt(), bytes)
|
||||||
setRegisterValue(Register.A0, start.toInt())
|
setRegisterValue(Register.A0, start.toInt())
|
||||||
setRegisterValue(Register.BP, start + bytes.size + 1)
|
setRegisterValue(Register.BP, start + bytes.size + 1)
|
||||||
@@ -953,12 +951,15 @@ class MonTanaMiniComputer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun rewind() {
|
fun rewind() {
|
||||||
val latestRewindStep = rewindSteps!!.pop()
|
if (rewindIndex >= 0) {
|
||||||
latestRewindStep.rewind()
|
val latestRewindStep = rewindSteps[rewindIndex]
|
||||||
|
rewindIndex--
|
||||||
|
latestRewindStep?.rewind()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val isBackAvailable: Boolean
|
val isBackAvailable: Boolean
|
||||||
get() = !rewindSteps!!.isEmpty()
|
get() = rewindIndex >= 0
|
||||||
|
|
||||||
enum class ComputerStatus {
|
enum class ComputerStatus {
|
||||||
READY,
|
READY,
|
||||||
@@ -1014,17 +1015,8 @@ class MonTanaMiniComputer {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
val isMcp = getBits(16, 8, instruction) == 5.toShort()
|
val isMcp = getBits(16, 8, instruction) == 5.toShort()
|
||||||
if (isMcp) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
return isMcp
|
||||||
fun main(args: Array<String>) {
|
|
||||||
val computer = MonTanaMiniComputer()
|
|
||||||
computer.speed = 1 // default to 1hz
|
|
||||||
computer.start()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,4 +73,4 @@ enum class Register {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
18
src/commonMain/kotlin/mtmc/emulator/RewindStep.kt
Normal file
18
src/commonMain/kotlin/mtmc/emulator/RewindStep.kt
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package mtmc.emulator
|
||||||
|
|
||||||
|
import mtmc.util.Runnable
|
||||||
|
|
||||||
|
class RewindStep {
|
||||||
|
var subSteps: Array<Runnable> = Array(10) { {} }
|
||||||
|
var index = 0
|
||||||
|
|
||||||
|
fun rewind() {
|
||||||
|
subSteps.reversed().forEach({ obj: Runnable? -> obj!!.invoke() })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addSubStep(subStep: Runnable?) {
|
||||||
|
if (subStep != null && index < subSteps.size) {
|
||||||
|
subSteps[index++] = subStep
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1727
src/commonMain/kotlin/mtmc/emulator/SplashData.kt
Normal file
1727
src/commonMain/kotlin/mtmc/emulator/SplashData.kt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,18 +3,17 @@ package mtmc.os
|
|||||||
import mtmc.emulator.MonTanaMiniComputer
|
import mtmc.emulator.MonTanaMiniComputer
|
||||||
import mtmc.emulator.Register
|
import mtmc.emulator.Register
|
||||||
import mtmc.os.SysCall.Companion.getValue
|
import mtmc.os.SysCall.Companion.getValue
|
||||||
|
import mtmc.os.fs.File
|
||||||
|
import mtmc.os.fs.Files
|
||||||
import mtmc.os.shell.Shell
|
import mtmc.os.shell.Shell
|
||||||
import java.io.File
|
import mtmc.util.currentTimeMillis
|
||||||
import java.io.IOException
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.util.*
|
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.text.Charsets.US_ASCII
|
import kotlin.random.Random
|
||||||
|
|
||||||
class MTOS(private val computer: MonTanaMiniComputer) {
|
class MTOS(private val computer: MonTanaMiniComputer) {
|
||||||
private var timer: Long = 0
|
private var timer: Double = 0.0
|
||||||
var random: Random = Random()
|
var random: Random = Random.Default
|
||||||
|
|
||||||
// Editor support
|
// Editor support
|
||||||
var currentFile: String? = null
|
var currentFile: String? = null
|
||||||
@@ -52,59 +51,59 @@ class MTOS(private val computer: MonTanaMiniComputer) {
|
|||||||
fun handleSysCall(syscallNumber: Short) {
|
fun handleSysCall(syscallNumber: Short) {
|
||||||
if (syscallNumber == getValue("exit").toShort()) {
|
if (syscallNumber == getValue("exit").toShort()) {
|
||||||
computer.setStatus(MonTanaMiniComputer.ComputerStatus.FINISHED)
|
computer.setStatus(MonTanaMiniComputer.ComputerStatus.FINISHED)
|
||||||
} else if (syscallNumber == getValue("rint").toShort()) {
|
// } else if (syscallNumber == getValue("rint").toShort()) {
|
||||||
// rint
|
// // rint
|
||||||
if (!computer.console.hasShortValue()) {
|
// if (!computer.console.hasShortValue()) {
|
||||||
computer.notifyOfRequestInteger()
|
// computer.notifyOfRequestInteger()
|
||||||
}
|
// }
|
||||||
while (!computer.console.hasShortValue() && computer.getStatus() == MonTanaMiniComputer.ComputerStatus.EXECUTING) {
|
// while (!computer.console.hasShortValue() && computer.getStatus() == MonTanaMiniComputer.ComputerStatus.EXECUTING) {
|
||||||
try {
|
// try {
|
||||||
Thread.sleep(10)
|
// Thread.sleep(10)
|
||||||
} catch (e: InterruptedException) {
|
// } catch (e: InterruptedException) {
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
val `val` = computer.console.readInt()
|
// val `val` = computer.console.readInt()
|
||||||
computer.setRegisterValue(Register.RV, `val`.toInt())
|
// computer.setRegisterValue(Register.RV, `val`.toInt())
|
||||||
} else if (syscallNumber == getValue("wint").toShort()) {
|
} else if (syscallNumber == getValue("wint").toShort()) {
|
||||||
// wint
|
// wint
|
||||||
val value = computer.getRegisterValue(Register.A0)
|
val value = computer.getRegisterValue(Register.A0)
|
||||||
computer.console.writeInt(value)
|
computer.console.writeInt(value)
|
||||||
} else if (syscallNumber == getValue("rchr").toShort()) {
|
// } else if (syscallNumber == getValue("rchr").toShort()) {
|
||||||
if (!computer.console.hasShortValue()) {
|
// if (!computer.console.hasShortValue()) {
|
||||||
computer.notifyOfRequestCharacter()
|
// computer.notifyOfRequestCharacter()
|
||||||
}
|
// }
|
||||||
while (!computer.console.hasShortValue() && computer.getStatus() == MonTanaMiniComputer.ComputerStatus.EXECUTING) {
|
// while (!computer.console.hasShortValue() && computer.getStatus() == MonTanaMiniComputer.ComputerStatus.EXECUTING) {
|
||||||
try {
|
// try {
|
||||||
Thread.sleep(10)
|
// Thread.sleep(10)
|
||||||
} catch (e: InterruptedException) {
|
// } catch (e: InterruptedException) {
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
val `val` = computer.console.readChar()
|
// val `val` = computer.console.readChar()
|
||||||
computer.setRegisterValue(Register.RV, `val`.code)
|
// computer.setRegisterValue(Register.RV, `val`.code)
|
||||||
} else if (syscallNumber == getValue("wchr").toShort()) {
|
} else if (syscallNumber == getValue("wchr").toShort()) {
|
||||||
val value = computer.getRegisterValue(Register.A0)
|
val value = computer.getRegisterValue(Register.A0)
|
||||||
computer.console.print("" + Char(value.toUShort()))
|
computer.console.print("" + Char(value.toUShort()))
|
||||||
} else if (syscallNumber == getValue("rstr").toShort()) {
|
// } else if (syscallNumber == getValue("rstr").toShort()) {
|
||||||
// rstr
|
// // rstr
|
||||||
val pointer = computer.getRegisterValue(Register.A0)
|
// val pointer = computer.getRegisterValue(Register.A0)
|
||||||
val maxLen = computer.getRegisterValue(Register.A1)
|
// val maxLen = computer.getRegisterValue(Register.A1)
|
||||||
if (!computer.console.hasReadString()) {
|
// if (!computer.console.hasReadString()) {
|
||||||
computer.notifyOfRequestString()
|
// computer.notifyOfRequestString()
|
||||||
}
|
// }
|
||||||
while (!computer.console.hasReadString() && computer.getStatus() == MonTanaMiniComputer.ComputerStatus.EXECUTING) {
|
// while (!computer.console.hasReadString() && computer.getStatus() == MonTanaMiniComputer.ComputerStatus.EXECUTING) {
|
||||||
try {
|
// try {
|
||||||
Thread.sleep(10)
|
// Thread.sleep(10)
|
||||||
} catch (e: InterruptedException) {
|
// } catch (e: InterruptedException) {
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
val string = computer.console.readString()
|
// val string = computer.console.readString()
|
||||||
val bytes = string!!.toByteArray(US_ASCII)
|
// val bytes = string!!.encodeToByteArray()
|
||||||
val bytesToRead = min(bytes.size, maxLen.toInt())
|
// val bytesToRead = min(bytes.size, maxLen.toInt())
|
||||||
for (i in 0..<bytesToRead) {
|
// for (i in 0..<bytesToRead) {
|
||||||
val aByte = bytes[i]
|
// val aByte = bytes[i]
|
||||||
computer.writeByteToMemory(pointer + i, aByte)
|
// computer.writeByteToMemory(pointer + i, aByte)
|
||||||
}
|
// }
|
||||||
computer.setRegisterValue(Register.RV, bytesToRead)
|
// computer.setRegisterValue(Register.RV, bytesToRead)
|
||||||
} else if (syscallNumber == getValue("wstr").toShort()) {
|
} else if (syscallNumber == getValue("wstr").toShort()) {
|
||||||
// wstr
|
// wstr
|
||||||
val pointer = computer.getRegisterValue(Register.A0)
|
val pointer = computer.getRegisterValue(Register.A0)
|
||||||
@@ -172,14 +171,14 @@ class MTOS(private val computer: MonTanaMiniComputer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
computer.setRegisterValue(Register.RV, random.nextInt(low.toInt(), high + 1))
|
computer.setRegisterValue(Register.RV, random.nextInt(low.toInt(), high + 1))
|
||||||
} else if (syscallNumber == getValue("sleep").toShort()) {
|
// } else if (syscallNumber == getValue("sleep").toShort()) {
|
||||||
// sleep
|
// // sleep
|
||||||
val millis = computer.getRegisterValue(Register.A0)
|
// val millis = computer.getRegisterValue(Register.A0)
|
||||||
try {
|
// try {
|
||||||
if (millis > 0) Thread.sleep(millis.toLong())
|
// if (millis > 0) Thread.sleep(millis.toLong())
|
||||||
} catch (e: InterruptedException) {
|
// } catch (e: InterruptedException) {
|
||||||
throw RuntimeException(e)
|
// throw RuntimeException(e)
|
||||||
}
|
// }
|
||||||
} else if (syscallNumber == getValue("fbreset").toShort()) {
|
} else if (syscallNumber == getValue("fbreset").toShort()) {
|
||||||
// fbreset
|
// fbreset
|
||||||
computer.display.reset()
|
computer.display.reset()
|
||||||
@@ -241,9 +240,40 @@ class MTOS(private val computer: MonTanaMiniComputer) {
|
|||||||
try {
|
try {
|
||||||
// special handling for game-of-life cell files
|
// special handling for game-of-life cell files
|
||||||
if ("cells" == fileType) {
|
if ("cells" == fileType) {
|
||||||
val str = Files.readString(file.toPath())
|
//val str = Files.readString(file.toPath())
|
||||||
val lines = Arrays
|
val str = """
|
||||||
.stream<String>(str.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray())
|
! galaxy
|
||||||
|
! Kok's galaxy - a whirling oscillator
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
...............OOOOOO.OO.....
|
||||||
|
...............OOOOOO.OO
|
||||||
|
......................OO
|
||||||
|
...............OO.....OO
|
||||||
|
...............OO.....OO
|
||||||
|
...............OO.....OO
|
||||||
|
...............OO.......
|
||||||
|
...............OO.OOOOOO
|
||||||
|
...............OO.OOOOOO.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
.......................................
|
||||||
|
""".trimIndent()
|
||||||
|
val lines: List<String> =
|
||||||
|
str.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||||
.filter { s: String? -> !s!!.startsWith("!") }
|
.filter { s: String? -> !s!!.startsWith("!") }
|
||||||
.toList()
|
.toList()
|
||||||
|
|
||||||
@@ -251,7 +281,7 @@ class MTOS(private val computer: MonTanaMiniComputer) {
|
|||||||
val cappedLines = min(linesTotal, maxSize2.toInt())
|
val cappedLines = min(linesTotal, maxSize2.toInt())
|
||||||
|
|
||||||
for (lineNum in 0..<cappedLines) {
|
for (lineNum in 0..<cappedLines) {
|
||||||
val line = lines.get(lineNum)
|
val line: String = lines.get(lineNum)
|
||||||
for (colNum in 0..<maxSize1) {
|
for (colNum in 0..<maxSize1) {
|
||||||
val offset = lineNum * maxSize1 + colNum
|
val offset = lineNum * maxSize1 + colNum
|
||||||
val byteOffset = offset / 8
|
val byteOffset = offset / 8
|
||||||
@@ -275,7 +305,7 @@ class MTOS(private val computer: MonTanaMiniComputer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
computer.setRegisterValue(Register.RV, 0)
|
computer.setRegisterValue(Register.RV, 0)
|
||||||
} catch (e: IOException) {
|
} catch (e: Exception) {
|
||||||
computer.setRegisterValue(Register.RV, -1)
|
computer.setRegisterValue(Register.RV, -1)
|
||||||
e.printStackTrace() // debugging
|
e.printStackTrace() // debugging
|
||||||
}
|
}
|
||||||
@@ -290,7 +320,6 @@ class MTOS(private val computer: MonTanaMiniComputer) {
|
|||||||
computer.writeByteToMemory(destination + i, aByte)
|
computer.writeByteToMemory(destination + i, aByte)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//TODO: Should this return the length with or without the null terminator?
|
//TODO: Should this return the length with or without the null terminator?
|
||||||
computer.writeByteToMemory(destination + maxSize - 1, 0.toByte())
|
computer.writeByteToMemory(destination + maxSize - 1, 0.toByte())
|
||||||
computer.setRegisterValue(Register.RV, maxSize - 1)
|
computer.setRegisterValue(Register.RV, maxSize - 1)
|
||||||
@@ -300,18 +329,18 @@ class MTOS(private val computer: MonTanaMiniComputer) {
|
|||||||
|
|
||||||
if (computer.fileSystem.exists(dir)) {
|
if (computer.fileSystem.exists(dir)) {
|
||||||
computer.setRegisterValue(Register.RV, 0)
|
computer.setRegisterValue(Register.RV, 0)
|
||||||
computer.fileSystem.setCWD(dir)
|
computer.fileSystem.cWD = dir
|
||||||
} else {
|
} else {
|
||||||
computer.setRegisterValue(Register.RV, 1)
|
computer.setRegisterValue(Register.RV, 1)
|
||||||
}
|
}
|
||||||
} else if (syscallNumber == getValue("timer").toShort()) {
|
} else if (syscallNumber == getValue("timer").toShort()) {
|
||||||
val value = computer.getRegisterValue(Register.A0)
|
val value = computer.getRegisterValue(Register.A0)
|
||||||
|
|
||||||
if (value > 0) this.timer = System.currentTimeMillis() + value
|
if (value > 0) this.timer = currentTimeMillis() + value
|
||||||
|
|
||||||
computer.setRegisterValue(
|
computer.setRegisterValue(
|
||||||
Register.RV,
|
Register.RV,
|
||||||
max(0, this.timer - System.currentTimeMillis()).toInt()
|
max(0.0, this.timer - currentTimeMillis()).toInt()
|
||||||
)
|
)
|
||||||
} else if (syscallNumber == getValue("drawimg").toShort()) {
|
} else if (syscallNumber == getValue("drawimg").toShort()) {
|
||||||
val image = computer.getRegisterValue(Register.A0)
|
val image = computer.getRegisterValue(Register.A0)
|
||||||
@@ -401,10 +430,13 @@ class MTOS(private val computer: MonTanaMiniComputer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val file = list[offset.toInt()]
|
val file = list[offset.toInt()]
|
||||||
val name = file.getName()
|
val name = file.name
|
||||||
val size = min(maxSizeOut - 1, name.length)
|
val size = min(maxSizeOut - 1, name.length)
|
||||||
|
|
||||||
computer.writeWordToMemory(destination.toInt(), if (file.isDirectory()) 1 else 0)
|
computer.writeWordToMemory(
|
||||||
|
destination.toInt(),
|
||||||
|
if (file.directory) 1 else 0
|
||||||
|
)
|
||||||
|
|
||||||
for (i in 0..<size) {
|
for (i in 0..<size) {
|
||||||
val aByte = name.get(i).code.toByte()
|
val aByte = name.get(i).code.toByte()
|
||||||
@@ -433,9 +465,9 @@ class MTOS(private val computer: MonTanaMiniComputer) {
|
|||||||
length++
|
length++
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
val outputString = String(computer.memory, pointer.toInt(), length.toInt(), US_ASCII)
|
val outputString = computer.memory.decodeToString(pointer.toInt(), pointer.toInt() + length)
|
||||||
return outputString
|
return outputString
|
||||||
} catch (ignored: StringIndexOutOfBoundsException) {
|
} catch (ignored: IndexOutOfBoundsException) {
|
||||||
computer.setStatus(MonTanaMiniComputer.ComputerStatus.PERMANENT_ERROR)
|
computer.setStatus(MonTanaMiniComputer.ComputerStatus.PERMANENT_ERROR)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -447,9 +479,12 @@ class MTOS(private val computer: MonTanaMiniComputer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadFile(path: String?): File {
|
fun loadFile(path: String?): File = File(path ?: "<empty>")
|
||||||
val fs = computer.fileSystem
|
/*
|
||||||
val file = fs.getRealPath(path).toFile()
|
{
|
||||||
return file
|
val fs = computer.fileSystem
|
||||||
}
|
val file = fs.getRealPath(path).toFile()
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
package mtmc.os
|
package mtmc.os
|
||||||
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
enum class SysCall(value: Int) {
|
enum class SysCall(value: Int) {
|
||||||
EXIT(0x00),
|
EXIT(0x00),
|
||||||
RINT(0x01),
|
RINT(0x01),
|
||||||
@@ -50,25 +48,24 @@ enum class SysCall(value: Int) {
|
|||||||
companion object {
|
companion object {
|
||||||
fun isSysCall(call: String): Boolean {
|
fun isSysCall(call: String): Boolean {
|
||||||
try {
|
try {
|
||||||
valueOf(call.uppercase(Locale.getDefault()))
|
valueOf(call.uppercase())
|
||||||
return true
|
return true
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun getValue(call: String): Byte {
|
fun getValue(call: String): Byte {
|
||||||
return valueOf(call.uppercase(Locale.getDefault())).value
|
return valueOf(call.uppercase()).value
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getString(syscallCode: Byte): String? {
|
fun getString(syscallCode: Byte): String? {
|
||||||
for (o in entries) {
|
for (o in entries) {
|
||||||
if (o.value == syscallCode) {
|
if (o.value == syscallCode) {
|
||||||
return o.name.lowercase(Locale.getDefault())
|
return o.name.lowercase()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
58
src/commonMain/kotlin/mtmc/os/exec/Executable.kt
Normal file
58
src/commonMain/kotlin/mtmc/os/exec/Executable.kt
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package mtmc.os.exec
|
||||||
|
|
||||||
|
import mtmc.emulator.DebugInfo
|
||||||
|
import mtmc.os.fs.Path
|
||||||
|
|
||||||
|
data class Executable(
|
||||||
|
val format: Format,
|
||||||
|
val code: ByteArray,
|
||||||
|
val data: ByteArray,
|
||||||
|
val graphics: Array<ByteArray>,
|
||||||
|
val sourceName: String,
|
||||||
|
val debugInfo: DebugInfo?
|
||||||
|
) {
|
||||||
|
enum class Format(val formatName: String) {
|
||||||
|
Orc1("orc1");
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dump(): String? {
|
||||||
|
return null //Gson().toJson(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dump(path: Path) {
|
||||||
|
/*
|
||||||
|
FileWriter(path.toFile()).use { fw ->
|
||||||
|
dump(fw)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
fun dump(writer: Writer?) {
|
||||||
|
val gson = Gson()
|
||||||
|
gson.toJson(this, writer)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun load(exe: String?): Executable? {
|
||||||
|
return null //Gson().fromJson<Executable?>(exe, Executable::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun load(path: Path): Executable? = null
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
FileReader(path.toFile()).use { fw ->
|
||||||
|
return load(fw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
fun load(reader: Reader): Executable? {
|
||||||
|
val gson = Gson()
|
||||||
|
return gson.fromJson<Executable?>(reader, Executable::class.java)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/commonMain/kotlin/mtmc/os/fs/Console.kt
Normal file
11
src/commonMain/kotlin/mtmc/os/fs/Console.kt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package mtmc.os.fs
|
||||||
|
|
||||||
|
object System {
|
||||||
|
val console: Console = Console()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Console {
|
||||||
|
fun readLine(prompt: String? = ""): String {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/commonMain/kotlin/mtmc/os/fs/File.kt
Normal file
43
src/commonMain/kotlin/mtmc/os/fs/File.kt
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package mtmc.os.fs
|
||||||
|
|
||||||
|
object Files {
|
||||||
|
fun readString(toPath: Path): String {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun readAllBytes(toPath: Path): ByteArray {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class File(
|
||||||
|
val parent: File? = null,
|
||||||
|
val name: String
|
||||||
|
) {
|
||||||
|
|
||||||
|
var directory: Boolean = false
|
||||||
|
|
||||||
|
constructor(name: String) : this(null, name)
|
||||||
|
|
||||||
|
fun getPath(): String = if (parent == null) {
|
||||||
|
name
|
||||||
|
} else {
|
||||||
|
"${parent.getPath()}/$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun exists(): Boolean = true
|
||||||
|
|
||||||
|
fun getAbsolutePath(): String {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toPath(): Path {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun listFiles(): Array<File> {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
213
src/commonMain/kotlin/mtmc/os/fs/FileSystem.kt
Normal file
213
src/commonMain/kotlin/mtmc/os/fs/FileSystem.kt
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
package mtmc.os.fs
|
||||||
|
|
||||||
|
import mtmc.emulator.MonTanaMiniComputer
|
||||||
|
import kotlin.jvm.JvmField
|
||||||
|
|
||||||
|
class FileSystem(
|
||||||
|
val computer: MonTanaMiniComputer
|
||||||
|
) {
|
||||||
|
private var cwd = "/home"
|
||||||
|
|
||||||
|
init {
|
||||||
|
initFileSystem()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initFileSystem() {
|
||||||
|
/*
|
||||||
|
if (DISK_PATH.toFile().exists()) return
|
||||||
|
|
||||||
|
|
||||||
|
// Make the disk/ directory
|
||||||
|
DISK_PATH.toFile().mkdirs()
|
||||||
|
|
||||||
|
try {
|
||||||
|
ZipInputStream(getClass().getResourceAsStream("/disk.zip")).use { `in` ->
|
||||||
|
var entry: ZipEntry?
|
||||||
|
var file: File?
|
||||||
|
|
||||||
|
val data = ByteArray(4096)
|
||||||
|
var count: Int
|
||||||
|
while ((`in`.getNextEntry().also { entry = it }) != null) {
|
||||||
|
file = File(entry.getName())
|
||||||
|
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
file.mkdirs()
|
||||||
|
} else {
|
||||||
|
file.getParentFile().mkdirs()
|
||||||
|
|
||||||
|
FileOutputStream(file).use { out ->
|
||||||
|
while ((`in`.read(data).also { count = it }) > 0) {
|
||||||
|
out.write(data, 0, count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun notifyOfFileSystemUpdate() {
|
||||||
|
if (this.computer != null) {
|
||||||
|
computer.notifyOfFileSystemUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var cWD: String
|
||||||
|
get() = this.cwd
|
||||||
|
set(cwd) {
|
||||||
|
this.cwd = resolve(cwd)
|
||||||
|
this.notifyOfFileSystemUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun exists(path: String): Boolean {
|
||||||
|
return false //File(DISK_PATH.toFile(), resolve(path)).exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mkdir(path: String): Boolean {
|
||||||
|
/*
|
||||||
|
val success: Boolean = File(DISK_PATH.toFile(), resolve(path)).mkdir()
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
computer.notifyOfFileSystemUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
return success
|
||||||
|
*/
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun delete(path: String): Boolean {
|
||||||
|
/*
|
||||||
|
val success: Boolean = File(DISK_PATH.toFile(), resolve(path)).delete()
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
computer.notifyOfFileSystemUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
return success
|
||||||
|
*/
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resolve(filename: String): String = filename
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
val root: File = DISK_PATH.toFile()
|
||||||
|
var directory = if (filename.startsWith("/")) root else File(root, cwd)
|
||||||
|
val path: Array<String> = filename.split("/")
|
||||||
|
|
||||||
|
for (name in path) {
|
||||||
|
if (name.equals(".")) {
|
||||||
|
continue
|
||||||
|
} else if (name.equals("..") && directory.equals(root)) {
|
||||||
|
continue
|
||||||
|
} else if (name.equals("..")) {
|
||||||
|
directory = directory.getParentFile()
|
||||||
|
} else {
|
||||||
|
directory = File(directory, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (directory.equals(root)) {
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
return directory.getAbsolutePath().substring(root.getAbsolutePath().length())
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
fun getRealPath(path: String): Path { // Resolves given path and returns /disk/ + path
|
||||||
|
val resolvedPath = resolve(path)
|
||||||
|
val slashGone: String = if (resolvedPath.length > 0) resolvedPath.substring(1) else ""
|
||||||
|
val file: File = DISK_PATH.resolve(slashGone).toFile()
|
||||||
|
|
||||||
|
if (file.getAbsolutePath().length < DISK_PATH.toFile().getAbsolutePath().length) {
|
||||||
|
return DISK_PATH
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.toPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFileList(path: String): Array<File> {
|
||||||
|
val resolvedPath: File = getRealPath(path).toFile()
|
||||||
|
|
||||||
|
if (!resolvedPath.directory) return arrayOf<File>(resolvedPath)
|
||||||
|
|
||||||
|
return resolvedPath.listFiles()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun listFiles(path: String): Listing {
|
||||||
|
val resolvedPath: File = getRealPath(path).toFile()
|
||||||
|
|
||||||
|
return Listing(this, resolvedPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun listCWD(): Listing {
|
||||||
|
return listFiles(cwd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun listRoot(): Listing {
|
||||||
|
return listFiles("/")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeFile(path: String, contents: String?) {
|
||||||
|
val filePath: Path = getRealPath(path)
|
||||||
|
//Files.writeString(filePath, contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun readFile(path: String): String? {
|
||||||
|
val filePath: Path = getRealPath(path)
|
||||||
|
val contents = "" //Files.readString(filePath)
|
||||||
|
return contents
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMimeType(path: String): String {
|
||||||
|
val file: Path = getRealPath(path)
|
||||||
|
val name = file.toFile().name.lowercase()
|
||||||
|
val probed = file.probeContentType()
|
||||||
|
|
||||||
|
if (name.endsWith(".asm")) return "text/x-asm"
|
||||||
|
if (name.endsWith(".c")) return "text/x-csrc"
|
||||||
|
if (name.endsWith(".sea")) return "text/x-csrc"
|
||||||
|
if (name.endsWith(".h")) return "text/x-csrc"
|
||||||
|
if (probed != null) return probed
|
||||||
|
|
||||||
|
return "application/octet-stream"
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
fun openFile(path: String): InputStream? {
|
||||||
|
val file: Unit */
|
||||||
|
/* TODO: class org.jetbrains.kotlin.nj2k.types.JKJavaNullPrimitiveType *//*
|
||||||
|
? =
|
||||||
|
getRealPath(path).toFile()
|
||||||
|
|
||||||
|
return FileInputStream(file)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
fun saveFile(path: String, contents: InputStream) {
|
||||||
|
val file: Unit */
|
||||||
|
/* TODO: class org.jetbrains.kotlin.nj2k.types.JKJavaNullPrimitiveType *//*
|
||||||
|
? =
|
||||||
|
getRealPath(path).toFile()
|
||||||
|
val data = ByteArray(4096)
|
||||||
|
var count: Int
|
||||||
|
|
||||||
|
FileOutputStream(file).use { out ->
|
||||||
|
while ((contents.read(data).also { count = it }) > 0) {
|
||||||
|
out.write(data, 0, count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmField
|
||||||
|
val DISK_PATH: Path = Path() //.of(System.getProperty("user.dir"), "disk").toAbsolutePath()
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/commonMain/kotlin/mtmc/os/fs/Listing.kt
Normal file
45
src/commonMain/kotlin/mtmc/os/fs/Listing.kt
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package mtmc.os.fs
|
||||||
|
|
||||||
|
class Listing(
|
||||||
|
val fs: FileSystem,
|
||||||
|
val file: File
|
||||||
|
) {
|
||||||
|
val path: String
|
||||||
|
val name: String
|
||||||
|
val directory: Boolean
|
||||||
|
val root: Boolean
|
||||||
|
|
||||||
|
init {
|
||||||
|
val root = FileSystem.DISK_PATH.toFile().getAbsolutePath().replace('\\', '/')
|
||||||
|
val path = file.getAbsolutePath().substring(root.length).replace('\\', '/')
|
||||||
|
|
||||||
|
this.path = if (path.length > 0) path else "/"
|
||||||
|
this.name = if (path.length > 0) file.name else "/"
|
||||||
|
this.directory = file.directory
|
||||||
|
this.root = this.path.equals("/")
|
||||||
|
}
|
||||||
|
|
||||||
|
val parent: Listing?
|
||||||
|
get() = if (file.parent != null) {
|
||||||
|
Listing(fs, file.parent)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun list(): List<Listing> {
|
||||||
|
if (!directory) {
|
||||||
|
return ArrayList()
|
||||||
|
}
|
||||||
|
|
||||||
|
val list = ArrayList<Listing>()
|
||||||
|
val children = file.listFiles()
|
||||||
|
|
||||||
|
list.sortBy { it.name }
|
||||||
|
|
||||||
|
for (child in children) {
|
||||||
|
list.add(Listing(fs, child))
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/commonMain/kotlin/mtmc/os/fs/Path.kt
Normal file
25
src/commonMain/kotlin/mtmc/os/fs/Path.kt
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package mtmc.os.fs
|
||||||
|
|
||||||
|
class Path {
|
||||||
|
fun toFile(): File {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun probeContentType(): String? {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resolve(filename: String): Path {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toAbsolutePath(): Path {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun of(string: String): Path {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
165
src/commonMain/kotlin/mtmc/os/shell/Shell.kt
Normal file
165
src/commonMain/kotlin/mtmc/os/shell/Shell.kt
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
package mtmc.os.shell
|
||||||
|
|
||||||
|
import mtmc.asm.Assembler
|
||||||
|
import mtmc.asm.instructions.Instruction.Companion.isInstruction
|
||||||
|
import mtmc.asm.peekFirst
|
||||||
|
import mtmc.emulator.MonTanaMiniComputer
|
||||||
|
import mtmc.emulator.Register
|
||||||
|
import mtmc.os.fs.FileSystem
|
||||||
|
import mtmc.os.shell.builtins.AssembleCommand
|
||||||
|
import mtmc.os.shell.builtins.DisplayCommand
|
||||||
|
import mtmc.os.shell.builtins.ExitCommand
|
||||||
|
import mtmc.os.shell.builtins.GetCommand
|
||||||
|
import mtmc.os.shell.builtins.HelpCommand
|
||||||
|
import mtmc.os.shell.builtins.LoadCommand
|
||||||
|
import mtmc.os.shell.builtins.PauseCommand
|
||||||
|
import mtmc.os.shell.builtins.RunCommand
|
||||||
|
import mtmc.os.shell.builtins.SeacCommand
|
||||||
|
import mtmc.os.shell.builtins.SetCommand
|
||||||
|
import mtmc.os.shell.builtins.SpeedCommand
|
||||||
|
import mtmc.os.shell.builtins.StepCommand
|
||||||
|
import mtmc.os.shell.builtins.WebCommand
|
||||||
|
import mtmc.tokenizer.MTMCToken
|
||||||
|
import mtmc.tokenizer.MTMCTokenizer
|
||||||
|
|
||||||
|
object Shell {
|
||||||
|
private val COMMANDS: MutableMap<String?, ShellCommand> = LinkedHashMap<String?, ShellCommand>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
COMMANDS.put("help", HelpCommand())
|
||||||
|
COMMANDS.put("exit", ExitCommand())
|
||||||
|
COMMANDS.put("set", SetCommand())
|
||||||
|
COMMANDS.put("get", GetCommand())
|
||||||
|
COMMANDS.put("web", WebCommand())
|
||||||
|
COMMANDS.put("disp", DisplayCommand())
|
||||||
|
COMMANDS.put("asm", AssembleCommand())
|
||||||
|
COMMANDS.put("load", LoadCommand())
|
||||||
|
COMMANDS.put("step", StepCommand())
|
||||||
|
COMMANDS.put("run", RunCommand())
|
||||||
|
COMMANDS.put("pause", PauseCommand())
|
||||||
|
COMMANDS.put("speed", SpeedCommand())
|
||||||
|
COMMANDS.put("sc", SeacCommand())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isCommand(cmd: String): Boolean {
|
||||||
|
return COMMANDS.containsKey(cmd.lowercase())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findExecutable(path: String?, fs: FileSystem): Boolean {
|
||||||
|
if (path == null || path == "") return false
|
||||||
|
//if (fs.exists(path) && !fs.listFiles(path).directory) return true
|
||||||
|
//if (fs.exists("/bin/" + path) && !fs.listFiles("/bin/" + path).directory) return true
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(Exception::class)
|
||||||
|
private fun runExecutable(
|
||||||
|
file: String,
|
||||||
|
command: String,
|
||||||
|
tokens: MTMCTokenizer,
|
||||||
|
computer: MonTanaMiniComputer
|
||||||
|
) {
|
||||||
|
val fs = computer.fileSystem
|
||||||
|
/*
|
||||||
|
var srcPath = Path.of("disk/" + fs.resolve(file))
|
||||||
|
if (!srcPath.toFile().exists()) {
|
||||||
|
srcPath = Path.of("disk" + fs.resolve("/bin/" + file))
|
||||||
|
}
|
||||||
|
val exec = load(srcPath)
|
||||||
|
computer.load(exec!!.code, exec.data, exec.graphics, exec.debugInfo)
|
||||||
|
tokens.consume()
|
||||||
|
val arg = command.substring(file.length).trim()
|
||||||
|
computer.oS.applyBreakpoints()
|
||||||
|
computer.setArg(arg)
|
||||||
|
computer.run()
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
fun execCommand(command: String, computer: MonTanaMiniComputer) {
|
||||||
|
println("Exec: $command")
|
||||||
|
val tokens = MTMCTokenizer(command, "#")
|
||||||
|
try {
|
||||||
|
val identifier = tokens.matchAndConsume(MTMCToken.TokenType.IDENTIFIER)
|
||||||
|
val cmd: String?
|
||||||
|
if (identifier == null) {
|
||||||
|
val question = tokens.matchAndConsume(MTMCToken.TokenType.QUESTION_MARK)
|
||||||
|
val executable = tokens.collapseTokensAsString()
|
||||||
|
|
||||||
|
|
||||||
|
// alias ? to help
|
||||||
|
if (question != null) {
|
||||||
|
cmd = "help"
|
||||||
|
} else if (findExecutable(executable, computer.fileSystem)) {
|
||||||
|
cmd = executable
|
||||||
|
} else {
|
||||||
|
printShellHelp(computer)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cmd = identifier.stringValue
|
||||||
|
}
|
||||||
|
if (isCommand(cmd)) {
|
||||||
|
COMMANDS[cmd.lowercase()]!!.exec(tokens, computer)
|
||||||
|
} else {
|
||||||
|
println("Executing: $command")
|
||||||
|
tokens.reset()
|
||||||
|
val asm = mutableListOf<MTMCToken>()
|
||||||
|
asm.addAll(tokens.tokens)
|
||||||
|
val updatedAsm = Assembler.transformSyntheticInstructions(asm)
|
||||||
|
val firstToken = updatedAsm.peekFirst()
|
||||||
|
val firstTokenStr = firstToken?.stringValue ?: error("Unexpected null token")
|
||||||
|
if (!updatedAsm.isEmpty() && isInstruction(firstTokenStr)) {
|
||||||
|
val assembler = Assembler()
|
||||||
|
val result = assembler.assemble(command)
|
||||||
|
if (result.errors.isEmpty()) {
|
||||||
|
val code = result.code
|
||||||
|
if (code.size == 4) {
|
||||||
|
val data = (code[2].toInt() shl 8) or code[3].toInt()
|
||||||
|
computer.setRegisterValue(Register.DR, data)
|
||||||
|
}
|
||||||
|
val lower = code[1].toInt() and 0xFF
|
||||||
|
val higher = code[0].toInt() and 0xFF
|
||||||
|
val inst = (higher shl 8) or lower
|
||||||
|
val originalDebugInfo = computer.debugInfo
|
||||||
|
computer.debugInfo = result.debugInfo
|
||||||
|
computer.execInstruction(inst.toShort())
|
||||||
|
computer.debugInfo = originalDebugInfo
|
||||||
|
} else {
|
||||||
|
computer.console.println(result.printErrors())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (findExecutable(cmd, computer.fileSystem)) {
|
||||||
|
runExecutable(cmd, command, tokens, computer)
|
||||||
|
} else {
|
||||||
|
printShellHelp(computer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
} catch (e: NoSuchFileException) {
|
||||||
|
computer.console.println("No such file: " + e.getFile())
|
||||||
|
*/
|
||||||
|
} catch (e: Exception) {
|
||||||
|
computer.console.println(e.message!!)
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun printShellHelp(computer: MonTanaMiniComputer) {
|
||||||
|
computer.console.println("Shell BuiltIns: \n")
|
||||||
|
for (value in COMMANDS.values) {
|
||||||
|
computer.console.println(value.help!!)
|
||||||
|
}
|
||||||
|
computer.console.println("Also: ")
|
||||||
|
computer.console.println(" <asm instruction>")
|
||||||
|
computer.console.println(
|
||||||
|
"or \n" +
|
||||||
|
" <executable>\n\n"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun printShellWelcome(computer: MonTanaMiniComputer) {
|
||||||
|
computer.console.println("Welcome to MtOS! Type ? for help")
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/commonMain/kotlin/mtmc/os/shell/UsageException.kt
Normal file
7
src/commonMain/kotlin/mtmc/os/shell/UsageException.kt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package mtmc.os.shell
|
||||||
|
|
||||||
|
class UsageException(
|
||||||
|
cmd: ShellCommand
|
||||||
|
) : RuntimeException(
|
||||||
|
"Usage:\n\n" + cmd.help
|
||||||
|
)
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package mtmc.os.shell.builtins
|
||||||
|
|
||||||
|
import mtmc.emulator.MonTanaMiniComputer
|
||||||
|
import mtmc.os.shell.ShellCommand
|
||||||
|
import mtmc.tokenizer.MTMCTokenizer
|
||||||
|
|
||||||
|
class AssembleCommand : ShellCommand() {
|
||||||
|
@Throws(Exception::class)
|
||||||
|
public override fun exec(tokens: MTMCTokenizer, computer: MonTanaMiniComputer) {
|
||||||
|
val fs = computer.fileSystem
|
||||||
|
val src = tokens.collapseTokensAsString()
|
||||||
|
require(!(src == null || src.isBlank())) { "missing or required argument 'src'" }
|
||||||
|
/*
|
||||||
|
val srcPath: Path = getDiskPath(src, fs)
|
||||||
|
|
||||||
|
val dst = tokens.collapseTokensAsString()
|
||||||
|
require(!(dst == null || dst.isBlank())) { "missing required argument 'dst'" }
|
||||||
|
val dstPath: Path = getDiskPath(dst, fs)
|
||||||
|
|
||||||
|
val contents = Files.readString(srcPath)
|
||||||
|
val assembler = Assembler()
|
||||||
|
val file_name =
|
||||||
|
fs.resolve(src) // srcPath.toString().substring(DISK_PATH.toString().length()).replaceAll("\\\\", "/");
|
||||||
|
val executable = assembler.assembleExecutable(file_name, contents)
|
||||||
|
executable.dump(dstPath)
|
||||||
|
computer.notifyOfFileSystemUpdate()
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
override val help: String
|
||||||
|
get() = """
|
||||||
|
asm <src> <dst>
|
||||||
|
- src : path to a .asm file
|
||||||
|
- dst : path to a target output binary
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/*
|
||||||
|
fun getDiskPath(pathString: String, fs: FileSystem): Path {
|
||||||
|
val path = Path.of("disk" + fs.resolve(pathString))
|
||||||
|
return path.toAbsolutePath()
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,15 +2,14 @@ package mtmc.os.shell.builtins
|
|||||||
|
|
||||||
import mtmc.emulator.MTMCDisplay
|
import mtmc.emulator.MTMCDisplay
|
||||||
import mtmc.emulator.MonTanaMiniComputer
|
import mtmc.emulator.MonTanaMiniComputer
|
||||||
|
import mtmc.os.fs.File
|
||||||
import mtmc.os.shell.ShellCommand
|
import mtmc.os.shell.ShellCommand
|
||||||
import mtmc.tokenizer.MTMCToken
|
import mtmc.tokenizer.MTMCToken
|
||||||
import mtmc.tokenizer.MTMCTokenizer
|
import mtmc.tokenizer.MTMCTokenizer
|
||||||
import java.io.File
|
import kotlin.random.Random
|
||||||
import java.util.*
|
|
||||||
import javax.imageio.ImageIO
|
|
||||||
|
|
||||||
class DisplayCommand : ShellCommand() {
|
class DisplayCommand : ShellCommand() {
|
||||||
var random: Random = Random()
|
var random: Random = Random.Default
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
public override fun exec(tokens: MTMCTokenizer, computer: MonTanaMiniComputer) {
|
public override fun exec(tokens: MTMCTokenizer, computer: MonTanaMiniComputer) {
|
||||||
@@ -42,8 +41,8 @@ class DisplayCommand : ShellCommand() {
|
|||||||
if (tokens.more()) {
|
if (tokens.more()) {
|
||||||
val imagePath = tokens.collapseTokensAsString()
|
val imagePath = tokens.collapseTokensAsString()
|
||||||
val file = computer.oS.loadFile(imagePath)
|
val file = computer.oS.loadFile(imagePath)
|
||||||
val img = ImageIO.read(file)
|
//val img = ImageIO.read(file)
|
||||||
computer.display.loadScaledImage(img)
|
//computer.display.loadScaledImage(img)
|
||||||
} else {
|
} else {
|
||||||
usageException()
|
usageException()
|
||||||
}
|
}
|
||||||
@@ -54,11 +53,15 @@ class DisplayCommand : ShellCommand() {
|
|||||||
} else if (tokens.match(MTMCToken.TokenType.INTEGER)) {
|
} else if (tokens.match(MTMCToken.TokenType.INTEGER)) {
|
||||||
val row = tokens.consumeAsInteger()
|
val row = tokens.consumeAsInteger()
|
||||||
val col = tokens.require(
|
val col = tokens.require(
|
||||||
mtmc.tokenizer.MTMCToken.TokenType.INTEGER,
|
mtmc.tokenizer.MTMCToken.TokenType.INTEGER
|
||||||
java.lang.Runnable { this.usageException() })!!.intValue()
|
) {
|
||||||
|
this.usageException()
|
||||||
|
}!!.intValue()
|
||||||
val color = tokens.require(
|
val color = tokens.require(
|
||||||
mtmc.tokenizer.MTMCToken.TokenType.INTEGER,
|
mtmc.tokenizer.MTMCToken.TokenType.INTEGER
|
||||||
java.lang.Runnable { this.usageException() })!!.intValue()
|
) {
|
||||||
|
this.usageException()
|
||||||
|
}!!.intValue()
|
||||||
computer.display.setPixel(row, col, color)
|
computer.display.setPixel(row, col, color)
|
||||||
} else {
|
} else {
|
||||||
usageException()
|
usageException()
|
||||||
@@ -7,7 +7,7 @@ import mtmc.tokenizer.MTMCTokenizer
|
|||||||
class ExitCommand : ShellCommand() {
|
class ExitCommand : ShellCommand() {
|
||||||
public override fun exec(tokens: MTMCTokenizer, computer: MonTanaMiniComputer) {
|
public override fun exec(tokens: MTMCTokenizer, computer: MonTanaMiniComputer) {
|
||||||
computer.console.println("Goodbye!")
|
computer.console.println("Goodbye!")
|
||||||
System.exit(1)
|
//System.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val help: String
|
override val help: String
|
||||||
@@ -17,11 +17,11 @@ class GetCommand : ShellCommand() {
|
|||||||
if (memLocation == null) {
|
if (memLocation == null) {
|
||||||
val register = tokens.matchAndConsume(MTMCToken.TokenType.IDENTIFIER)
|
val register = tokens.matchAndConsume(MTMCToken.TokenType.IDENTIFIER)
|
||||||
if (register == null) usageException()
|
if (register == null) usageException()
|
||||||
val reg = Register.toInteger(register!!.stringValue())
|
val reg = Register.toInteger(register!!.stringValue)
|
||||||
if (reg >= 0) {
|
if (reg >= 0) {
|
||||||
computer.console.println(register.stringValue() + ": " + computer.getRegisterValue(reg))
|
computer.console.println(register.stringValue + ": " + computer.getRegisterValue(reg))
|
||||||
} else {
|
} else {
|
||||||
throw IllegalArgumentException("Bad register: " + register.stringValue())
|
throw IllegalArgumentException("Bad register: " + register.stringValue)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (memLocation.type === MTMCToken.TokenType.INTEGER || memLocation.type === MTMCToken.TokenType.BINARY || memLocation.type === MTMCToken.TokenType.HEX) {
|
if (memLocation.type === MTMCToken.TokenType.INTEGER || memLocation.type === MTMCToken.TokenType.BINARY || memLocation.type === MTMCToken.TokenType.HEX) {
|
||||||
@@ -3,13 +3,13 @@ package mtmc.os.shell.builtins
|
|||||||
import mtmc.emulator.MonTanaMiniComputer
|
import mtmc.emulator.MonTanaMiniComputer
|
||||||
import mtmc.os.exec.Executable.Companion.load
|
import mtmc.os.exec.Executable.Companion.load
|
||||||
import mtmc.os.fs.FileSystem
|
import mtmc.os.fs.FileSystem
|
||||||
|
import mtmc.os.fs.Path
|
||||||
import mtmc.os.shell.ShellCommand
|
import mtmc.os.shell.ShellCommand
|
||||||
import mtmc.tokenizer.MTMCTokenizer
|
import mtmc.tokenizer.MTMCTokenizer
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
class LoadCommand : ShellCommand() {
|
class LoadCommand : ShellCommand() {
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
public override fun exec(tokens: MTMCTokenizer, computer: MonTanaMiniComputer) {
|
override fun exec(tokens: MTMCTokenizer, computer: MonTanaMiniComputer) {
|
||||||
val fs = computer.fileSystem
|
val fs = computer.fileSystem
|
||||||
val program = tokens.collapseTokensAsString()
|
val program = tokens.collapseTokensAsString()
|
||||||
require(!(program == null || program.isBlank())) { "missing or required argument 'src'" }
|
require(!(program == null || program.isBlank())) { "missing or required argument 'src'" }
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
package mtmc.os.shell.builtins
|
package mtmc.os.shell.builtins
|
||||||
|
|
||||||
import mtmc.emulator.MonTanaMiniComputer
|
import mtmc.emulator.MonTanaMiniComputer
|
||||||
import mtmc.lang.sea.SeaLanguage
|
|
||||||
import mtmc.os.shell.ShellCommand
|
import mtmc.os.shell.ShellCommand
|
||||||
import mtmc.tokenizer.MTMCTokenizer
|
import mtmc.tokenizer.MTMCTokenizer
|
||||||
import mtmc.util.StringEscapeUtils.escapeString
|
|
||||||
|
|
||||||
class SeacCommand : ShellCommand() {
|
class SeacCommand : ShellCommand() {
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
@@ -24,15 +22,18 @@ class SeacCommand : ShellCommand() {
|
|||||||
|
|
||||||
requireNotNull(filename) { "expected source file" }
|
requireNotNull(filename) { "expected source file" }
|
||||||
|
|
||||||
require(fs.exists(filename)) { "file " + escapeString(filename) + " does not exist" }
|
require(fs.exists(filename)) { "file " + filename + " does not exist" }
|
||||||
println(fs.resolve(filename))
|
println(fs.resolve(filename))
|
||||||
val lang = SeaLanguage()
|
|
||||||
val content = fs.readFile(filename)
|
|
||||||
val exec = lang.compileExecutable(fs.resolve(filename), content)
|
|
||||||
|
|
||||||
val bin = exec.dump()
|
/*
|
||||||
computer.fileSystem.writeFile(output, bin)
|
val lang = SeaLanguage()
|
||||||
computer.notifyOfFileSystemUpdate()
|
val content = fs.readFile(filename)
|
||||||
|
val exec = lang.compileExecutable(fs.resolve(filename), content)
|
||||||
|
|
||||||
|
val bin = exec.dump()
|
||||||
|
computer.fileSystem.writeFile(output, bin)
|
||||||
|
computer.notifyOfFileSystemUpdate()
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
override val help: String
|
override val help: String
|
||||||
@@ -23,11 +23,11 @@ class SetCommand : ShellCommand() {
|
|||||||
MTMCToken.TokenType.BINARY
|
MTMCToken.TokenType.BINARY
|
||||||
)
|
)
|
||||||
if (value == null) usageException()
|
if (value == null) usageException()
|
||||||
val reg = Register.toInteger(register!!.stringValue())
|
val reg = Register.toInteger(register!!.stringValue)
|
||||||
if (reg >= 0) {
|
if (reg >= 0) {
|
||||||
computer.setRegisterValue(reg, value!!.intValue())
|
computer.setRegisterValue(reg, value!!.intValue())
|
||||||
} else {
|
} else {
|
||||||
throw IllegalArgumentException("Bad register: " + register.stringValue())
|
throw IllegalArgumentException("Bad register: " + register.stringValue)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val value = tokens.matchAndConsume(
|
val value = tokens.matchAndConsume(
|
||||||
@@ -40,7 +40,10 @@ class SetCommand : ShellCommand() {
|
|||||||
if (value?.type === MTMCToken.TokenType.INTEGER || value?.type === MTMCToken.TokenType.BINARY || value?.type === MTMCToken.TokenType.HEX) {
|
if (value?.type === MTMCToken.TokenType.INTEGER || value?.type === MTMCToken.TokenType.BINARY || value?.type === MTMCToken.TokenType.HEX) {
|
||||||
computer.writeWordToMemory(memLocation.intValue(), value.intValue())
|
computer.writeWordToMemory(memLocation.intValue(), value.intValue())
|
||||||
} else {
|
} else {
|
||||||
computer.writeStringToMemory(memLocation.intValue(), value!!.stringValue().toByteArray())
|
computer.writeStringToMemory(
|
||||||
|
memLocation.intValue(),
|
||||||
|
value!!.stringValue.encodeToByteArray()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,13 +4,10 @@ import mtmc.emulator.MonTanaMiniComputer
|
|||||||
import mtmc.os.shell.ShellCommand
|
import mtmc.os.shell.ShellCommand
|
||||||
import mtmc.tokenizer.MTMCToken
|
import mtmc.tokenizer.MTMCToken
|
||||||
import mtmc.tokenizer.MTMCTokenizer
|
import mtmc.tokenizer.MTMCTokenizer
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class SpeedCommand : ShellCommand() {
|
class SpeedCommand : ShellCommand() {
|
||||||
private val speeds: MutableList<Int?> = Arrays.asList<Int?>(
|
private val speeds = listOf(
|
||||||
*arrayOf<Int>(
|
|
||||||
1, 10, 100, 1000, 10000, 100000, 1000000
|
1, 10, 100, 1000, 10000, 100000, 1000000
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
@@ -1,14 +1,16 @@
|
|||||||
package mtmc.tokenizer
|
package mtmc.tokenizer
|
||||||
|
|
||||||
import java.util.*
|
fun Char.isHexDigit(): Boolean {
|
||||||
|
return this in '0'..'9' || this in 'a'..'f' || this in 'A'..'F'
|
||||||
|
}
|
||||||
|
|
||||||
class MTMCScanner(private val src: String, private val lineCommentStart: String) {
|
class MTMCScanner(private val src: String, private val lineCommentStart: String) {
|
||||||
var position: Int = 0
|
var position: Int = 0
|
||||||
var line: Int = 1
|
var line: Int = 1
|
||||||
var lineOffset: Int = 0
|
var lineOffset: Int = 0
|
||||||
var tokens: LinkedList<MTMCToken> = LinkedList<MTMCToken>()
|
var tokens = mutableListOf<MTMCToken>()
|
||||||
|
|
||||||
fun tokenize(): LinkedList<MTMCToken> {
|
fun tokenize(): MutableList<MTMCToken> {
|
||||||
consumeWhitespace()
|
consumeWhitespace()
|
||||||
while (!scanEnd()) {
|
while (!scanEnd()) {
|
||||||
scanToken()
|
scanToken()
|
||||||
@@ -38,7 +40,7 @@ class MTMCScanner(private val src: String, private val lineCommentStart: String)
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun scanLineComment(): Boolean {
|
private fun scanLineComment(): Boolean {
|
||||||
val bytes = lineCommentStart.toByteArray()
|
val bytes = lineCommentStart.encodeToByteArray()
|
||||||
for (i in bytes.indices) {
|
for (i in bytes.indices) {
|
||||||
val aChar = bytes[i]
|
val aChar = bytes[i]
|
||||||
if (peek(i).code.toByte() != aChar) {
|
if (peek(i).code.toByte() != aChar) {
|
||||||
@@ -188,7 +190,7 @@ class MTMCScanner(private val src: String, private val lineCommentStart: String)
|
|||||||
private fun scanHex(start: Int): Boolean {
|
private fun scanHex(start: Int): Boolean {
|
||||||
takeChar() // take leading zero
|
takeChar() // take leading zero
|
||||||
takeChar() // take 'x'
|
takeChar() // take 'x'
|
||||||
while (HexFormat.isHexDigit(peek().code)) {
|
while (peek().isHexDigit()) {
|
||||||
takeChar()
|
takeChar()
|
||||||
}
|
}
|
||||||
tokens.add(makeToken(MTMCToken.TokenType.HEX, strValueFrom(start), start))
|
tokens.add(makeToken(MTMCToken.TokenType.HEX, strValueFrom(start), start))
|
||||||
@@ -1,22 +1,17 @@
|
|||||||
package mtmc.tokenizer
|
package mtmc.tokenizer
|
||||||
|
|
||||||
@JvmRecord
|
|
||||||
data class MTMCToken(
|
data class MTMCToken(
|
||||||
@JvmField val start: Int,
|
val start: Int,
|
||||||
@JvmField val end: Int,
|
val end: Int,
|
||||||
val line: Int,
|
val line: Int,
|
||||||
val lineOffset: Int,
|
val lineOffset: Int,
|
||||||
@JvmField val stringValue: String,
|
val stringValue: String,
|
||||||
@JvmField val type: TokenType?
|
val type: TokenType?
|
||||||
) {
|
) {
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return stringValue
|
return stringValue
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stringValue(): String {
|
|
||||||
return stringValue
|
|
||||||
}
|
|
||||||
|
|
||||||
fun charValue(): Char {
|
fun charValue(): Char {
|
||||||
return stringValue.get(0)
|
return stringValue.get(0)
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
package mtmc.tokenizer
|
package mtmc.tokenizer
|
||||||
|
|
||||||
import java.util.*
|
import mtmc.util.Runnable
|
||||||
import java.util.stream.Stream
|
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
class MTMCTokenizer(var source: String, lineCommentStart: String) {
|
class MTMCTokenizer(var source: String, lineCommentStart: String) {
|
||||||
var tokens: LinkedList<MTMCToken> = MTMCScanner(source, lineCommentStart).tokenize()
|
var tokens: MutableList<MTMCToken> = MTMCScanner(source, lineCommentStart).tokenize()
|
||||||
var currentToken: Int = 0
|
var currentToken: Int = 0
|
||||||
|
|
||||||
fun currentToken(): MTMCToken {
|
fun getCurrentToken(): MTMCToken {
|
||||||
return tokens.get(currentToken)
|
return tokens.get(currentToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,8 +28,8 @@ class MTMCTokenizer(var source: String, lineCommentStart: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun matchAndConsume(identifier: String?): Boolean {
|
fun matchAndConsume(identifier: String?): Boolean {
|
||||||
if (currentToken().type == MTMCToken.TokenType.IDENTIFIER &&
|
if (getCurrentToken().type == MTMCToken.TokenType.IDENTIFIER &&
|
||||||
currentToken().stringValue == identifier
|
getCurrentToken().stringValue == identifier
|
||||||
) {
|
) {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
@@ -40,7 +39,7 @@ class MTMCTokenizer(var source: String, lineCommentStart: String) {
|
|||||||
|
|
||||||
fun match(vararg type: MTMCToken.TokenType?): Boolean {
|
fun match(vararg type: MTMCToken.TokenType?): Boolean {
|
||||||
for (tokenType in type) {
|
for (tokenType in type) {
|
||||||
if (currentToken().type == tokenType) {
|
if (getCurrentToken().type == tokenType) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,17 +51,13 @@ class MTMCTokenizer(var source: String, lineCommentStart: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun more(): Boolean {
|
fun more(): Boolean {
|
||||||
return currentToken().type != MTMCToken.TokenType.EOF
|
return getCurrentToken().type != MTMCToken.TokenType.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
fun previousToken(): MTMCToken? {
|
fun previousToken(): MTMCToken? {
|
||||||
return tokens.get(max(0, currentToken - 1))
|
return tokens.get(max(0, currentToken - 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stream(): Stream<MTMCToken?> {
|
|
||||||
return tokens.stream()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
val sb = StringBuilder()
|
val sb = StringBuilder()
|
||||||
for (i in tokens.indices) {
|
for (i in tokens.indices) {
|
||||||
@@ -88,7 +83,7 @@ class MTMCTokenizer(var source: String, lineCommentStart: String) {
|
|||||||
do {
|
do {
|
||||||
last = consume()
|
last = consume()
|
||||||
sb.append(last.stringValue)
|
sb.append(last.stringValue)
|
||||||
next = currentToken()
|
next = getCurrentToken()
|
||||||
} while (more() && last.end == next.start)
|
} while (more() && last.end == next.start)
|
||||||
return sb.toString()
|
return sb.toString()
|
||||||
} else {
|
} else {
|
||||||
@@ -104,7 +99,7 @@ class MTMCTokenizer(var source: String, lineCommentStart: String) {
|
|||||||
if (match(tokenType)) {
|
if (match(tokenType)) {
|
||||||
return consume()
|
return consume()
|
||||||
} else {
|
} else {
|
||||||
notFound.run()
|
notFound.invoke()
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,38 +11,28 @@ object BinaryUtils {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
val returnValue = (value.toInt() and 0xffff) ushr (start - totalBits)
|
val returnValue = (value.toInt() and 0xffff) ushr (start - totalBits)
|
||||||
var mask = 0
|
var mask = 1
|
||||||
var toShift = totalBits
|
mask = mask shl totalBits
|
||||||
while (toShift > 0) {
|
mask--
|
||||||
toShift--
|
|
||||||
mask = mask shl 1
|
|
||||||
mask = mask + 1
|
|
||||||
}
|
|
||||||
return (returnValue and mask).toShort()
|
return (returnValue and mask).toShort()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toBinary(aByte: Byte): String {
|
fun toBinary(aByte: Byte): String {
|
||||||
val binaryString = Integer.toBinaryString(aByte.toInt())
|
val zeroed = aByte.toInt().toString(2).padStart(8, '0')
|
||||||
val formatted = String.format("%8s", binaryString)
|
|
||||||
val zeroed = formatted.replace(" ".toRegex(), "0")
|
|
||||||
val underScored = zeroed.replace("....".toRegex(), "$0_")
|
val underScored = zeroed.replace("....".toRegex(), "$0_")
|
||||||
val noTrailingUnderscore = underScored.substring(0, underScored.length - 1)
|
val noTrailingUnderscore = underScored.substring(0, underScored.length - 1)
|
||||||
return "0b" + noTrailingUnderscore
|
return "0b" + noTrailingUnderscore
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toBinary(aShort: Short): String {
|
fun toBinary(aShort: Short): String {
|
||||||
val binaryString = Integer.toBinaryString(aShort.toInt())
|
val zeroed = aShort.toInt().toString(2).padStart(16, '0')
|
||||||
val formatted = String.format("%16s", binaryString)
|
|
||||||
val zeroed = formatted.replace(" ".toRegex(), "0")
|
|
||||||
val underScored = zeroed.replace("....".toRegex(), "$0_")
|
val underScored = zeroed.replace("....".toRegex(), "$0_")
|
||||||
val noTrailingUnderscore = underScored.substring(0, underScored.length - 1)
|
val noTrailingUnderscore = underScored.substring(0, underScored.length - 1)
|
||||||
return "0b" + noTrailingUnderscore
|
return "0b" + noTrailingUnderscore
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toBinary(anInt: Int): String {
|
fun toBinary(anInt: Int): String {
|
||||||
val binaryString = Integer.toBinaryString(anInt)
|
val zeroed = anInt.toString(2).padStart(32, '0')
|
||||||
val formatted = String.format("%32s", binaryString)
|
|
||||||
val zeroed = formatted.replace(" ".toRegex(), "0")
|
|
||||||
val underScored = zeroed.replace("....".toRegex(), "$0_")
|
val underScored = zeroed.replace("....".toRegex(), "$0_")
|
||||||
val noTrailingUnderscore = underScored.substring(0, underScored.length - 1)
|
val noTrailingUnderscore = underScored.substring(0, underScored.length - 1)
|
||||||
return "0b" + noTrailingUnderscore
|
return "0b" + noTrailingUnderscore
|
||||||
11
src/commonMain/kotlin/mtmc/util/PlatformSpecific.kt
Normal file
11
src/commonMain/kotlin/mtmc/util/PlatformSpecific.kt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package mtmc.util
|
||||||
|
|
||||||
|
expect fun currentTimeMillis(): Double
|
||||||
|
|
||||||
|
expect fun time(): Double
|
||||||
|
|
||||||
|
expect fun requestAnimationFrame(action: (Double) -> Unit)
|
||||||
|
|
||||||
|
expect fun setTimeout(action: () -> Unit)
|
||||||
|
|
||||||
|
expect fun immediateTimeout(action: (Double) -> Unit): Int
|
||||||
3
src/commonMain/kotlin/mtmc/util/Runnable.kt
Normal file
3
src/commonMain/kotlin/mtmc/util/Runnable.kt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package mtmc.util
|
||||||
|
|
||||||
|
typealias Runnable = () -> Unit
|
||||||
4817
src/jsMain/kotlin/mtmc/Code.kt
Normal file
4817
src/jsMain/kotlin/mtmc/Code.kt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,18 +1,43 @@
|
|||||||
package mtmc
|
package mtmc
|
||||||
|
|
||||||
import kotlinx.browser.document
|
import kotlinx.browser.document
|
||||||
import kotlinx.html.div
|
import kotlinx.browser.window
|
||||||
import nl.astraeus.komp.HtmlBuilder
|
import mtmc.emulator.MonTanaMiniComputer
|
||||||
|
import mtmc.util.currentTimeMillis
|
||||||
|
import mtmc.view.DisplayView
|
||||||
|
import mtmc.view.MTMCView
|
||||||
import nl.astraeus.komp.Komponent
|
import nl.astraeus.komp.Komponent
|
||||||
|
|
||||||
class HelloKomponent : Komponent() {
|
val computer = MonTanaMiniComputer()
|
||||||
override fun HtmlBuilder.render() {
|
val mainView = MTMCView(computer)
|
||||||
div {
|
val display = DisplayView(computer)
|
||||||
+ "Hello, world!"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
Komponent.create(document.body!!, HelloKomponent())
|
computer.speed = 1000000
|
||||||
|
computer.load(lifeCode, lifeData)
|
||||||
|
//computer.load(snakeCode, snakeData)
|
||||||
|
|
||||||
|
Komponent.create(document.body!!, mainView)
|
||||||
|
|
||||||
|
computer.start()
|
||||||
|
|
||||||
|
mainView.requestUpdate()
|
||||||
|
display.requestUpdate()
|
||||||
|
|
||||||
|
window.requestAnimationFrame { updateJsDisplay() }
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastMemoryUpdate = currentTimeMillis()
|
||||||
|
var updateState = true
|
||||||
|
|
||||||
|
fun updateJsDisplay() {
|
||||||
|
display.requestUpdate()
|
||||||
|
if (currentTimeMillis() - lastMemoryUpdate > 125) {
|
||||||
|
if (updateState) {
|
||||||
|
mainView.registerView.requestUpdate()
|
||||||
|
mainView.memoryView.requestUpdate()
|
||||||
|
}
|
||||||
|
lastMemoryUpdate = currentTimeMillis()
|
||||||
|
}
|
||||||
|
window.requestAnimationFrame { updateJsDisplay() }
|
||||||
}
|
}
|
||||||
|
|||||||
116
src/jsMain/kotlin/mtmc/emulator/BufferedImage.js.kt
Normal file
116
src/jsMain/kotlin/mtmc/emulator/BufferedImage.js.kt
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
package mtmc.emulator
|
||||||
|
|
||||||
|
import kotlinx.browser.document
|
||||||
|
import org.khronos.webgl.Uint8Array
|
||||||
|
import org.khronos.webgl.get
|
||||||
|
import org.khronos.webgl.set
|
||||||
|
import org.w3c.dom.CanvasRenderingContext2D
|
||||||
|
import org.w3c.dom.HTMLCanvasElement
|
||||||
|
import org.w3c.dom.ImageData
|
||||||
|
|
||||||
|
actual fun createBufferedImage(
|
||||||
|
width: Int,
|
||||||
|
height: Int
|
||||||
|
): BufferedImage = BufferedImageImpl(width, height, 1)
|
||||||
|
|
||||||
|
class BufferedImageImpl(
|
||||||
|
override val width: Int,
|
||||||
|
override val height: Int,
|
||||||
|
val type: Int
|
||||||
|
) : BufferedImage {
|
||||||
|
val display = ByteArray(width * height * 4)
|
||||||
|
|
||||||
|
override fun getRGB(x: Int, y: Int): Int {
|
||||||
|
check(x in 0 until width && y in 0 until height)
|
||||||
|
|
||||||
|
val offset = (x + y * width) * 4
|
||||||
|
return display[offset + 0].toInt() +
|
||||||
|
display[offset + 1].toInt() shl 8 +
|
||||||
|
display[offset + 2].toInt() shl 16
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setRGB(x: Int, y: Int, intVal: Int) {
|
||||||
|
check(x in 0 until width && y in 0 until height)
|
||||||
|
|
||||||
|
val offset = (x + y * width) * 4
|
||||||
|
display[offset + 0] = (intVal shr 16).toByte()
|
||||||
|
display[offset + 1] = (intVal shr 8).toByte()
|
||||||
|
display[offset + 2] = intVal.toByte()
|
||||||
|
display[offset + 3] = 255.toByte()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BufferedImageData(
|
||||||
|
val imageData: ImageData,
|
||||||
|
) : BufferedImage {
|
||||||
|
override val width: Int = imageData.width
|
||||||
|
override val height: Int = imageData.height
|
||||||
|
|
||||||
|
override fun getRGB(x: Int, y: Int): Int {
|
||||||
|
check(x in 0 until width && y in 0 until height)
|
||||||
|
|
||||||
|
val offset = (x * 4 + y * width * 4)
|
||||||
|
val display = imageData.data
|
||||||
|
return (display[offset + 0].toInt() and 0xff) shl 16 +
|
||||||
|
(display[offset + 1].toInt() and 0xff) shl 8 +
|
||||||
|
(display[offset + 2].toInt() and 0xff)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setRGB(x: Int, y: Int, intVal: Int) {
|
||||||
|
check(x in 0 until width && y in 0 until height)
|
||||||
|
|
||||||
|
val offset = (x * 4 + y * width * 4)
|
||||||
|
val display = imageData.data
|
||||||
|
display[offset + 0] = ((intVal shr 16) and 0xff).toShort().asDynamic()
|
||||||
|
display[offset + 1] = ((intVal shr 8) and 0xff).toShort().asDynamic()
|
||||||
|
display[offset + 2] = (intVal and 0xff).toShort().asDynamic()
|
||||||
|
display[offset + 3] = 255.toShort().asDynamic()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BufferedImageDataWebGl(
|
||||||
|
override val width: Int,
|
||||||
|
override val height: Int,
|
||||||
|
val data: Uint8Array = Uint8Array(width * height * 4)
|
||||||
|
) : BufferedImage {
|
||||||
|
init {
|
||||||
|
for (x in 0 until width) {
|
||||||
|
for (y in 0 until height) {
|
||||||
|
val offset = (x * 4 + y * width * 4)
|
||||||
|
data[offset + 3] = 255.toShort().asDynamic()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRGB(x: Int, y: Int): Int {
|
||||||
|
check(x in 0 until width && y in 0 until height)
|
||||||
|
|
||||||
|
val offset = (x * 4 + y * width * 4)
|
||||||
|
val display = data
|
||||||
|
return (display[offset + 0].toInt() and 0xff) shl 16 +
|
||||||
|
(display[offset + 1].toInt() and 0xff) shl 8 +
|
||||||
|
(display[offset + 2].toInt() and 0xff) shl 0 + 255
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setRGB(x: Int, y: Int, intVal: Int) {
|
||||||
|
check(x in 0 until width && y in 0 until height)
|
||||||
|
|
||||||
|
val offset = (x * 4 + y * width * 4)
|
||||||
|
|
||||||
|
data[offset + 0] = ((intVal shr 16) and 0xff).toShort().asDynamic()
|
||||||
|
data[offset + 1] = ((intVal shr 8) and 0xff).toShort().asDynamic()
|
||||||
|
data[offset + 2] = ((intVal shr 0) and 0xff).toShort().asDynamic()
|
||||||
|
//data[offset + 3] = 255.toShort().asDynamic()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val canvas = document.createElement("canvas") as HTMLCanvasElement
|
||||||
|
val ctx = canvas.getContext("2d") as CanvasRenderingContext2D
|
||||||
|
|
||||||
|
actual fun createCanvasImage(width: Int, height: Int): BufferedImage {
|
||||||
|
return BufferedImageData(ctx.createImageData(width.toDouble(), height.toDouble()));
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun createGLImage(width: Int, height: Int): BufferedImage {
|
||||||
|
return BufferedImageDataWebGl(width, height)
|
||||||
|
}
|
||||||
19
src/jsMain/kotlin/mtmc/util/PlatformSpecific.js.kt
Normal file
19
src/jsMain/kotlin/mtmc/util/PlatformSpecific.js.kt
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package mtmc.util
|
||||||
|
|
||||||
|
import kotlinx.browser.window
|
||||||
|
import kotlin.js.Date
|
||||||
|
|
||||||
|
actual fun currentTimeMillis(): Double = Date().getTime()
|
||||||
|
actual fun requestAnimationFrame(action: (Double) -> Unit) {
|
||||||
|
window.requestAnimationFrame {
|
||||||
|
action(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun immediateTimeout(action: (Double) -> Unit): Int = window.setTimeout(action, 0)
|
||||||
|
|
||||||
|
actual fun time(): Double = window.performance.now()
|
||||||
|
|
||||||
|
actual fun setTimeout(action: () -> Unit) {
|
||||||
|
window.setTimeout(action)
|
||||||
|
}
|
||||||
86
src/jsMain/kotlin/mtmc/view/ConsoleView.kt
Normal file
86
src/jsMain/kotlin/mtmc/view/ConsoleView.kt
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
package mtmc.view
|
||||||
|
|
||||||
|
import kotlinx.browser.window
|
||||||
|
import kotlinx.html.div
|
||||||
|
import kotlinx.html.input
|
||||||
|
import kotlinx.html.js.onClickFunction
|
||||||
|
import kotlinx.html.js.onKeyUpFunction
|
||||||
|
import kotlinx.html.span
|
||||||
|
import mtmc.display
|
||||||
|
import mtmc.emulator.MonTanaMiniComputer
|
||||||
|
import mtmc.mainView
|
||||||
|
import mtmc.os.shell.Shell
|
||||||
|
import nl.astraeus.komp.HtmlBuilder
|
||||||
|
import nl.astraeus.komp.Komponent
|
||||||
|
import nl.astraeus.komp.currentElement
|
||||||
|
import org.w3c.dom.HTMLInputElement
|
||||||
|
import org.w3c.dom.events.KeyboardEvent
|
||||||
|
|
||||||
|
class ConsoleView(
|
||||||
|
val computer: MonTanaMiniComputer
|
||||||
|
) : Komponent() {
|
||||||
|
var input: String = ""
|
||||||
|
var output = StringBuilder()
|
||||||
|
private var inputElement: HTMLInputElement? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
output.append(computer.console.consumeLines())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun HtmlBuilder.render() {
|
||||||
|
div("console") {
|
||||||
|
onClickFunction = {
|
||||||
|
inputElement?.focus()
|
||||||
|
}
|
||||||
|
div("console-history") {
|
||||||
|
div {
|
||||||
|
+output.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div("console-input") {
|
||||||
|
span("console-prompt") {
|
||||||
|
+"mtmc$"
|
||||||
|
}
|
||||||
|
input(classes = "console-input") {
|
||||||
|
value = input
|
||||||
|
autoFocus = true
|
||||||
|
inputElement = currentElement() as? HTMLInputElement
|
||||||
|
currentElement().scrollIntoView()
|
||||||
|
window.setTimeout({
|
||||||
|
inputElement?.focus()
|
||||||
|
}, 0)
|
||||||
|
onKeyUpFunction = { it ->
|
||||||
|
(it as? KeyboardEvent)?.let { ke ->
|
||||||
|
(ke.target as? HTMLInputElement)?.let { inp ->
|
||||||
|
if (ke.key == "Enter") {
|
||||||
|
handleCommand()
|
||||||
|
} else {
|
||||||
|
input = inp.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleCommand() {
|
||||||
|
computer.console.print("mtmc$ ")
|
||||||
|
computer.console.println(input)
|
||||||
|
|
||||||
|
Shell.execCommand(input, computer)
|
||||||
|
|
||||||
|
input = ""
|
||||||
|
output.append(computer.console.consumeLines())
|
||||||
|
if (output.length > 1000 && output.contains("\n")) {
|
||||||
|
output = output.deleteRange(0, output.indexOf("\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
mainView.registerView.requestUpdate()
|
||||||
|
mainView.memoryView.requestUpdate()
|
||||||
|
display.requestUpdate()
|
||||||
|
requestUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
157
src/jsMain/kotlin/mtmc/view/ControlView.kt
Normal file
157
src/jsMain/kotlin/mtmc/view/ControlView.kt
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
package mtmc.view
|
||||||
|
|
||||||
|
import kotlinx.html.InputType
|
||||||
|
import kotlinx.html.button
|
||||||
|
import kotlinx.html.div
|
||||||
|
import kotlinx.html.i
|
||||||
|
import kotlinx.html.input
|
||||||
|
import kotlinx.html.js.onChangeFunction
|
||||||
|
import kotlinx.html.js.onClickFunction
|
||||||
|
import kotlinx.html.label
|
||||||
|
import kotlinx.html.option
|
||||||
|
import kotlinx.html.select
|
||||||
|
import kotlinx.html.span
|
||||||
|
import mtmc.display
|
||||||
|
import mtmc.emulator.MonTanaMiniComputer
|
||||||
|
import mtmc.mainView
|
||||||
|
import mtmc.updateState
|
||||||
|
import nl.astraeus.komp.HtmlBuilder
|
||||||
|
import nl.astraeus.komp.Komponent
|
||||||
|
import org.w3c.dom.HTMLSelectElement
|
||||||
|
import kotlin.text.Typography.nbsp
|
||||||
|
|
||||||
|
class ControlView(
|
||||||
|
val computer: MonTanaMiniComputer
|
||||||
|
) : Komponent() {
|
||||||
|
|
||||||
|
override fun HtmlBuilder.render() {
|
||||||
|
div("control-panel") {
|
||||||
|
div("control-header") {
|
||||||
|
span {
|
||||||
|
+"m"
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
+"s"
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
+"u"
|
||||||
|
}
|
||||||
|
nbsp
|
||||||
|
i {
|
||||||
|
+"MTMC-16"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div("control-secondary") {
|
||||||
|
+"MonTana Mini-Computer"
|
||||||
|
}
|
||||||
|
div("control-buttons") {
|
||||||
|
label {
|
||||||
|
+"Update:"
|
||||||
|
htmlFor = "update-state"
|
||||||
|
input {
|
||||||
|
type = InputType.checkBox
|
||||||
|
checked = updateState
|
||||||
|
onClickFunction = {
|
||||||
|
updateState = !updateState
|
||||||
|
requestUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
select {
|
||||||
|
name = "speed"
|
||||||
|
|
||||||
|
option {
|
||||||
|
value = "1"
|
||||||
|
+"1 Hz"
|
||||||
|
}
|
||||||
|
option {
|
||||||
|
value = "10"
|
||||||
|
+"10 Hz"
|
||||||
|
}
|
||||||
|
option {
|
||||||
|
value = "100"
|
||||||
|
+"100 Hz"
|
||||||
|
}
|
||||||
|
option {
|
||||||
|
value = "1000"
|
||||||
|
+"1 Khz"
|
||||||
|
}
|
||||||
|
option {
|
||||||
|
value = "1000000"
|
||||||
|
selected = true
|
||||||
|
+"1 Mhz"
|
||||||
|
}
|
||||||
|
option {
|
||||||
|
value = "2000000"
|
||||||
|
+"2 Mhz"
|
||||||
|
}
|
||||||
|
option {
|
||||||
|
value = "5000000"
|
||||||
|
+"5 Mhz"
|
||||||
|
}
|
||||||
|
option {
|
||||||
|
value = "10000000"
|
||||||
|
+"10 Mhz"
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangeFunction = {
|
||||||
|
val target = it.target as? HTMLSelectElement
|
||||||
|
target?.value?.toIntOrNull()?.let { speed ->
|
||||||
|
computer.speed = speed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buttons with fx-* attributes
|
||||||
|
button {
|
||||||
|
if (computer.getStatus() != MonTanaMiniComputer.ComputerStatus.EXECUTING) {
|
||||||
|
+"run"
|
||||||
|
onClickFunction = {
|
||||||
|
computer.run()
|
||||||
|
requestUpdate()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
+"pause"
|
||||||
|
onClickFunction = {
|
||||||
|
computer.pause()
|
||||||
|
requestUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
disabled =
|
||||||
|
computer.getStatus() == MonTanaMiniComputer.ComputerStatus.WAITING && computer.rewindIndex >= 0
|
||||||
|
|
||||||
|
+"back"
|
||||||
|
|
||||||
|
onClickFunction = {
|
||||||
|
computer.back()
|
||||||
|
mainView.requestUpdate()
|
||||||
|
display.requestUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
+"step"
|
||||||
|
|
||||||
|
onClickFunction = {
|
||||||
|
computer.step()
|
||||||
|
mainView.requestUpdate()
|
||||||
|
display.requestUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
+"reset"
|
||||||
|
|
||||||
|
onClickFunction = {
|
||||||
|
computer.initMemory()
|
||||||
|
mainView.requestUpdate()
|
||||||
|
display.requestUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
173
src/jsMain/kotlin/mtmc/view/DisplayView.kt
Normal file
173
src/jsMain/kotlin/mtmc/view/DisplayView.kt
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
package mtmc.view
|
||||||
|
|
||||||
|
import kotlinx.html.canvas
|
||||||
|
import kotlinx.html.div
|
||||||
|
import mtmc.display
|
||||||
|
import mtmc.emulator.BufferedImageData
|
||||||
|
import mtmc.emulator.BufferedImageDataWebGl
|
||||||
|
import mtmc.emulator.MonTanaMiniComputer
|
||||||
|
import nl.astraeus.komp.HtmlBuilder
|
||||||
|
import nl.astraeus.komp.Komponent
|
||||||
|
import nl.astraeus.komp.currentElement
|
||||||
|
import org.khronos.webgl.Float32Array
|
||||||
|
import org.khronos.webgl.WebGLBuffer
|
||||||
|
import org.khronos.webgl.WebGLProgram
|
||||||
|
import org.khronos.webgl.WebGLRenderingContext
|
||||||
|
import org.khronos.webgl.WebGLShader
|
||||||
|
import org.khronos.webgl.WebGLTexture
|
||||||
|
import org.w3c.dom.HTMLCanvasElement
|
||||||
|
|
||||||
|
// language=GLSL
|
||||||
|
val vertexShader = """
|
||||||
|
attribute vec2 a_pos;
|
||||||
|
attribute vec2 a_uv;
|
||||||
|
varying vec2 v_uv;
|
||||||
|
void main() {
|
||||||
|
v_uv = a_uv;
|
||||||
|
gl_Position = vec4(a_pos, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
// language=GLSL
|
||||||
|
val fragmentShader = """
|
||||||
|
precision mediump float;
|
||||||
|
varying vec2 v_uv;
|
||||||
|
uniform sampler2D u_tex;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_FragColor = texture2D(u_tex, v_uv);
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
typealias GL = WebGLRenderingContext
|
||||||
|
|
||||||
|
class DiplayControlView(
|
||||||
|
val computer: MonTanaMiniComputer
|
||||||
|
) : Komponent() {
|
||||||
|
|
||||||
|
override fun HtmlBuilder.render() {
|
||||||
|
div("display") {
|
||||||
|
include(display)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DisplayView(
|
||||||
|
val computer: MonTanaMiniComputer
|
||||||
|
) : Komponent() {
|
||||||
|
var ctx: WebGLRenderingContext? = null
|
||||||
|
var program: WebGLProgram? = null
|
||||||
|
var texture: WebGLTexture? = null
|
||||||
|
var buffer: WebGLBuffer? = null
|
||||||
|
|
||||||
|
override fun HtmlBuilder.render() {
|
||||||
|
canvas("display-canvas") {
|
||||||
|
width = "160px"
|
||||||
|
height = "144px"
|
||||||
|
|
||||||
|
val cv = currentElement() as? HTMLCanvasElement
|
||||||
|
|
||||||
|
ctx = cv?.getContext("webgl")?.unsafeCast<WebGLRenderingContext>()
|
||||||
|
|
||||||
|
if (program == null) {
|
||||||
|
createProgram()
|
||||||
|
createBuffer()
|
||||||
|
createTexture()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createTexture() {
|
||||||
|
ctx?.let { gl: WebGLRenderingContext ->
|
||||||
|
texture = gl.createTexture()
|
||||||
|
gl.bindTexture(GL.TEXTURE_2D, texture)
|
||||||
|
|
||||||
|
// Set texture parameters for pixel-perfect rendering
|
||||||
|
gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE)
|
||||||
|
gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE)
|
||||||
|
gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.NEAREST)
|
||||||
|
gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.NEAREST)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createBuffer() {
|
||||||
|
ctx?.let { gl ->
|
||||||
|
// Create quad vertices
|
||||||
|
val vertices = Float32Array(
|
||||||
|
arrayOf(
|
||||||
|
-1f, -1f, 0f, 1f, // position, texCoord
|
||||||
|
1f, -1f, 1f, 1f,
|
||||||
|
-1f, 1f, 0f, 0f,
|
||||||
|
1f, 1f, 1f, 0f
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
buffer = gl.createBuffer();
|
||||||
|
gl.bindBuffer(GL.ARRAY_BUFFER, buffer);
|
||||||
|
gl.bufferData(GL.ARRAY_BUFFER, vertices, GL.STATIC_DRAW);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun createProgram() {
|
||||||
|
val vs = createShader(WebGLRenderingContext.VERTEX_SHADER, vertexShader)
|
||||||
|
val fs = createShader(WebGLRenderingContext.FRAGMENT_SHADER, fragmentShader)
|
||||||
|
val prog = ctx?.createProgram()
|
||||||
|
if (vs != null && fs != null && prog != null) {
|
||||||
|
ctx?.attachShader(prog, vs)
|
||||||
|
ctx?.attachShader(prog, fs)
|
||||||
|
ctx?.linkProgram(prog)
|
||||||
|
}
|
||||||
|
program = prog
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createShader(type: Int, source: String): WebGLShader? {
|
||||||
|
var result: WebGLShader? = null
|
||||||
|
ctx?.let { gl ->
|
||||||
|
result = gl.createShader(type)
|
||||||
|
result?.let { shader ->
|
||||||
|
gl.shaderSource(shader, source)
|
||||||
|
gl.compileShader(shader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun renderUpdate() {
|
||||||
|
// move data to canvas
|
||||||
|
val buffer = computer.display.buffer
|
||||||
|
if (buffer is BufferedImageData) {
|
||||||
|
//ctx?.putImageData(buffer.imageData, 0.0, 0.0)
|
||||||
|
} else if (buffer is BufferedImageDataWebGl) {
|
||||||
|
ctx?.let { gl ->
|
||||||
|
gl.clear(GL.COLOR_BUFFER_BIT)
|
||||||
|
|
||||||
|
gl.useProgram(program)
|
||||||
|
|
||||||
|
val positionLocation = gl.getAttribLocation(program, "a_pos")
|
||||||
|
val texCoordLocation = gl.getAttribLocation(program, "a_uv")
|
||||||
|
|
||||||
|
gl.bindBuffer(GL.ARRAY_BUFFER, this.buffer)
|
||||||
|
gl.enableVertexAttribArray(positionLocation)
|
||||||
|
gl.vertexAttribPointer(positionLocation, 2, GL.FLOAT, false, 16, 0)
|
||||||
|
gl.enableVertexAttribArray(texCoordLocation)
|
||||||
|
gl.vertexAttribPointer(texCoordLocation, 2, GL.FLOAT, false, 16, 8)
|
||||||
|
gl.bindTexture(GL.TEXTURE_2D, texture);
|
||||||
|
gl.texImage2D(
|
||||||
|
GL.TEXTURE_2D,
|
||||||
|
0, // level
|
||||||
|
GL.RGBA, // internal format
|
||||||
|
buffer.width,
|
||||||
|
buffer.height,
|
||||||
|
0, // border
|
||||||
|
GL.RGBA, // format
|
||||||
|
GL.UNSIGNED_BYTE, // type
|
||||||
|
buffer.data // data
|
||||||
|
)
|
||||||
|
|
||||||
|
// Draw quad
|
||||||
|
gl.drawArrays(GL.TRIANGLE_STRIP, 0, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/jsMain/kotlin/mtmc/view/MTMCView.kt
Normal file
34
src/jsMain/kotlin/mtmc/view/MTMCView.kt
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package mtmc.view
|
||||||
|
|
||||||
|
import kotlinx.html.div
|
||||||
|
import mtmc.emulator.MonTanaMiniComputer
|
||||||
|
import nl.astraeus.komp.HtmlBuilder
|
||||||
|
import nl.astraeus.komp.Komponent
|
||||||
|
|
||||||
|
class MTMCView(
|
||||||
|
val computer: MonTanaMiniComputer
|
||||||
|
) : Komponent() {
|
||||||
|
val controlView = ControlView(computer)
|
||||||
|
val registerView = RegisterView(computer)
|
||||||
|
val memoryView = MemoryView(computer)
|
||||||
|
val displayView = DiplayControlView(computer)
|
||||||
|
val consoleView = ConsoleView(computer)
|
||||||
|
|
||||||
|
override fun HtmlBuilder.render() {
|
||||||
|
div("container") {
|
||||||
|
div("left-column") {
|
||||||
|
include(controlView)
|
||||||
|
include(registerView)
|
||||||
|
include(memoryView)
|
||||||
|
}
|
||||||
|
div("middle-column") {
|
||||||
|
include(displayView)
|
||||||
|
include(consoleView)
|
||||||
|
}
|
||||||
|
div("right-column") {
|
||||||
|
+"Files"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
147
src/jsMain/kotlin/mtmc/view/MemoryView.kt
Normal file
147
src/jsMain/kotlin/mtmc/view/MemoryView.kt
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
package mtmc.view
|
||||||
|
|
||||||
|
import kotlinx.html.classes
|
||||||
|
import kotlinx.html.div
|
||||||
|
import kotlinx.html.table
|
||||||
|
import kotlinx.html.td
|
||||||
|
import kotlinx.html.title
|
||||||
|
import kotlinx.html.tr
|
||||||
|
import mtmc.asm.instructions.Instruction
|
||||||
|
import mtmc.emulator.MonTanaMiniComputer
|
||||||
|
import mtmc.emulator.Register
|
||||||
|
import nl.astraeus.komp.HtmlBuilder
|
||||||
|
import nl.astraeus.komp.Komponent
|
||||||
|
|
||||||
|
fun ByteArray.asHex(address: Int): String {
|
||||||
|
val value = getShort(address)
|
||||||
|
return value.toHexString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ByteArray.getShort(address: Int): Short {
|
||||||
|
val value = (this[address].toInt() and 0xff) * 256 + (this[address + 1].toInt() and 0xff)
|
||||||
|
return value.toShort()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum class DisplayFormat {
|
||||||
|
DYN,
|
||||||
|
HEX,
|
||||||
|
DEC,
|
||||||
|
INS,
|
||||||
|
STR
|
||||||
|
}
|
||||||
|
|
||||||
|
private val ASCII_CODES = arrayOf<String>(
|
||||||
|
"NUL", "SOH", "STX", "ETX",
|
||||||
|
"EOT", "ENT", "ACK", "BEL",
|
||||||
|
"BS", "\\t", "\\n", "VT",
|
||||||
|
"\\f", "\\r", "SO", "SI",
|
||||||
|
"DLE", "DC1", "DC2", "DC3",
|
||||||
|
"DC4", "NAK", "SYN", "ETB",
|
||||||
|
"CAN", "EM", "SUB", "ESC",
|
||||||
|
"FS", "GS", "RS", "US",
|
||||||
|
)
|
||||||
|
|
||||||
|
class MemoryView(
|
||||||
|
val computer: MonTanaMiniComputer
|
||||||
|
) : Komponent() {
|
||||||
|
var displayFormat: DisplayFormat = DisplayFormat.DYN
|
||||||
|
|
||||||
|
override fun HtmlBuilder.render() {
|
||||||
|
div("memory-panel") {
|
||||||
|
div("memory-header") {
|
||||||
|
+"Memory"
|
||||||
|
}
|
||||||
|
div("memory") {
|
||||||
|
table {
|
||||||
|
for (baseAddress in 0..<computer.memory.size step 16) {
|
||||||
|
tr {
|
||||||
|
var previousInstruction = 0x0fff.toShort()
|
||||||
|
for (offset in 0..<8) {
|
||||||
|
val address = baseAddress + offset * 2
|
||||||
|
val memoryClass = classFor(address)
|
||||||
|
val df = computeMemoryFormat(memoryClass)
|
||||||
|
val value = computer.memory.getShort(address)
|
||||||
|
|
||||||
|
td {
|
||||||
|
when (memoryClass) {
|
||||||
|
"curr" -> classes += "current-address"
|
||||||
|
"code" -> classes += "code"
|
||||||
|
"data" -> classes += "data"
|
||||||
|
"heap" -> classes += "heap"
|
||||||
|
"sta" -> classes += "stack"
|
||||||
|
}
|
||||||
|
|
||||||
|
title = address.toString() + " - " + value.toHexString()
|
||||||
|
|
||||||
|
when (df) {
|
||||||
|
DisplayFormat.DEC -> +value.toInt().toString()
|
||||||
|
DisplayFormat.INS -> {
|
||||||
|
+Instruction.disassemble(
|
||||||
|
value,
|
||||||
|
previousInstruction
|
||||||
|
)
|
||||||
|
previousInstruction = value
|
||||||
|
}
|
||||||
|
|
||||||
|
DisplayFormat.HEX -> +value.toHexString()
|
||||||
|
DisplayFormat.STR -> +get1252String(value)
|
||||||
|
else -> +"?"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun classFor(address: Int): String = if (address >= computer.getRegisterValue(Register.SP)) {
|
||||||
|
"sta"
|
||||||
|
} else if (address == computer.getRegisterValue(Register.PC).toInt()) {
|
||||||
|
"curr"
|
||||||
|
} else if (address <= computer.getRegisterValue(Register.CB)) {
|
||||||
|
"code"
|
||||||
|
} else if (address <= computer.getRegisterValue(Register.DB)) {
|
||||||
|
"data"
|
||||||
|
} else if (address <= computer.getRegisterValue(Register.BP)) {
|
||||||
|
"heap"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun computeMemoryFormat(memoryClass: String?): DisplayFormat {
|
||||||
|
return if (displayFormat == DisplayFormat.DYN) {
|
||||||
|
when (memoryClass) {
|
||||||
|
"sta" -> DisplayFormat.DEC
|
||||||
|
"code", "curr" -> DisplayFormat.INS
|
||||||
|
"data", "heap" -> DisplayFormat.STR
|
||||||
|
else -> DisplayFormat.HEX
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
displayFormat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun get1252String(value: Short): String {
|
||||||
|
val topByte = (value.toInt() ushr 8).toByte()
|
||||||
|
val bottomByte = value.toByte()
|
||||||
|
|
||||||
|
return if (topByte.toInt() != 0) {
|
||||||
|
get1252String(topByte) + " " + get1252String(
|
||||||
|
bottomByte
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
get1252String(bottomByte)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun get1252String(value: Byte): String = if (value in 0..<32) {
|
||||||
|
ASCII_CODES[value.toInt()]
|
||||||
|
} else {
|
||||||
|
"" + (value.toInt() and 0xff).toChar()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
117
src/jsMain/kotlin/mtmc/view/RegisterView.kt
Normal file
117
src/jsMain/kotlin/mtmc/view/RegisterView.kt
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
package mtmc.view
|
||||||
|
|
||||||
|
import kotlinx.browser.document
|
||||||
|
import kotlinx.dom.addClass
|
||||||
|
import kotlinx.dom.removeClass
|
||||||
|
import kotlinx.html.TABLE
|
||||||
|
import kotlinx.html.classes
|
||||||
|
import kotlinx.html.div
|
||||||
|
import kotlinx.html.hr
|
||||||
|
import kotlinx.html.id
|
||||||
|
import kotlinx.html.span
|
||||||
|
import kotlinx.html.table
|
||||||
|
import kotlinx.html.td
|
||||||
|
import kotlinx.html.tr
|
||||||
|
import mtmc.emulator.MonTanaMiniComputer
|
||||||
|
import mtmc.emulator.Register
|
||||||
|
import nl.astraeus.komp.HtmlBuilder
|
||||||
|
import nl.astraeus.komp.Komponent
|
||||||
|
import org.w3c.dom.HTMLElement
|
||||||
|
|
||||||
|
class RegisterView(
|
||||||
|
val computer: MonTanaMiniComputer
|
||||||
|
) : Komponent() {
|
||||||
|
|
||||||
|
override fun HtmlBuilder.render() {
|
||||||
|
div {
|
||||||
|
table("register-table") {
|
||||||
|
for (index in 0..<16) {
|
||||||
|
showRegister(index)
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td {
|
||||||
|
colSpan = "3"
|
||||||
|
hr {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
showRegister(16)
|
||||||
|
showRegister(17)
|
||||||
|
tr {
|
||||||
|
td("flags") {
|
||||||
|
colSpan = "3"
|
||||||
|
span {
|
||||||
|
+"flags t:"
|
||||||
|
|
||||||
|
div("blinken") {
|
||||||
|
id = "flags-t"
|
||||||
|
if (!computer.isFlagTestBitSet) {
|
||||||
|
classes += "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
+"o:"
|
||||||
|
div("blinken") {
|
||||||
|
id = "flags-o"
|
||||||
|
classes += "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
+"e:"
|
||||||
|
div("blinken") {
|
||||||
|
id = "flags-e"
|
||||||
|
if (computer.getStatus() != MonTanaMiniComputer.ComputerStatus.PERMANENT_ERROR) {
|
||||||
|
classes += "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun renderUpdate() {
|
||||||
|
for (register in 0..<18) {
|
||||||
|
val value = computer.registerFile[register]
|
||||||
|
for (bit in 15 downTo 0) {
|
||||||
|
val element = document.getElementById("bit-$bit-$register") as? HTMLElement ?: continue
|
||||||
|
if (value.toInt() and (1 shl bit) == 0) {
|
||||||
|
element.addClass("off")
|
||||||
|
} else {
|
||||||
|
element.removeClass("off")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val element = document.getElementById("register-$register") as? HTMLElement ?: continue
|
||||||
|
element.innerText = "${value.toInt() and 0xffff}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun TABLE.showRegister(register: Int) {
|
||||||
|
val name = Register.fromInteger(register)
|
||||||
|
val value = computer.registerFile[register]
|
||||||
|
tr {
|
||||||
|
td {
|
||||||
|
+name
|
||||||
|
}
|
||||||
|
td("register-lights") {
|
||||||
|
for (bit in 15 downTo 0) {
|
||||||
|
div("blinken") {
|
||||||
|
id = "bit-$bit-$register"
|
||||||
|
if (value.toInt() and (1 shl bit) == 0) {
|
||||||
|
classes += "off"
|
||||||
|
}
|
||||||
|
if (bit % 4 == 0) {
|
||||||
|
classes += "space"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
td("align-right") {
|
||||||
|
id = "register-$register"
|
||||||
|
+"${value.toInt() and 0xffff}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
256
src/jsMain/resources/mtmc.css
Normal file
256
src/jsMain/resources/mtmc.css
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
:root {
|
||||||
|
--pdp-blue: #243571;
|
||||||
|
--pdp-light-blue: #3286ce;
|
||||||
|
--pdp-beige: #fdfddc;
|
||||||
|
--pdp-white: #f1f1f6;
|
||||||
|
--pdp-off-white: #F0EBCD;
|
||||||
|
--filetree-gray: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
overscroll-behavior: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 450px 450px 1fr;
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
overflow-y: auto;
|
||||||
|
gap: 5px;
|
||||||
|
background-color: var(--pdp-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-column {
|
||||||
|
grid-template-rows: auto auto minmax(0, 1fr);
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 4px;
|
||||||
|
height: 100dvh;
|
||||||
|
max-height: 100dvh;
|
||||||
|
min-width: 450px;
|
||||||
|
max-width: 450px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.middle-column {
|
||||||
|
background-color: #eee;
|
||||||
|
grid-template-rows: auto minmax(0, 1fr);
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 4px;
|
||||||
|
height: 100dvh;
|
||||||
|
max-height: 100dvh;
|
||||||
|
min-width: 450px;
|
||||||
|
max-width: 450px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-column {
|
||||||
|
background-color: #eee;
|
||||||
|
grid-template-rows: auto;
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 4px;
|
||||||
|
height: 100dvh;
|
||||||
|
max-height: 100dvh;
|
||||||
|
min-width: 450px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* control */
|
||||||
|
|
||||||
|
.control-panel > * {
|
||||||
|
margin: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-header {
|
||||||
|
background-color: var(--pdp-blue);
|
||||||
|
color: var(--pdp-white);
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 32px;
|
||||||
|
padding: 4px 24px;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-header span {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: -6px;
|
||||||
|
margin-bottom: -6px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 38px;
|
||||||
|
border-right: 4px solid white;
|
||||||
|
border-left: 4px solid white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-header span:not(:first-child) {
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-secondary {
|
||||||
|
background-color: var(--pdp-light-blue);
|
||||||
|
color: var(--pdp-white);
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 18px;
|
||||||
|
font-style: italic;
|
||||||
|
text-align: center;
|
||||||
|
padding: 4px 4px;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-buttons {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* registers */
|
||||||
|
|
||||||
|
table.register-table {
|
||||||
|
font-family: monospace;
|
||||||
|
width: 100%;
|
||||||
|
padding: 5px;
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.register-table tr td.align-right {
|
||||||
|
text-align: right;
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.register-table tr td.register-lights {
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.register-table tr td.flags {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.register-table tr td.flags span {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.register-table tr td.flags span .blinken {
|
||||||
|
margin-left: 4px;
|
||||||
|
top: 2px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blinken {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 2px;
|
||||||
|
height: 6px;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 3px;
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blinken.off {
|
||||||
|
background-color: #5c0119;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blinken.space {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* memory */
|
||||||
|
|
||||||
|
.memory-panel {
|
||||||
|
background-color: #eee;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory-header {
|
||||||
|
font-family: monospace;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
|
/* overflow: auto; */
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory table tr td {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-address {
|
||||||
|
background-color: #78e878;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
background-color: darkseagreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data {
|
||||||
|
background-color: lightgoldenrodyellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heap {
|
||||||
|
background-color: lightcoral;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stack {
|
||||||
|
background-color: lightsalmon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* display */
|
||||||
|
|
||||||
|
.display {
|
||||||
|
min-height: 480px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.display-canvas {
|
||||||
|
padding: 24px 64px;
|
||||||
|
width: 320px;
|
||||||
|
height: 288px;
|
||||||
|
image-rendering: pixelated; /* Keeps sharp pixels, no smoothing */
|
||||||
|
image-rendering: -moz-crisp-edges; /* Firefox */
|
||||||
|
image-rendering: crisp-edges; /* Standard */
|
||||||
|
}
|
||||||
|
/* console */
|
||||||
|
|
||||||
|
.console {
|
||||||
|
color: #ffcc00;
|
||||||
|
background-color: #35291c;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
font-family: monospace;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 5px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-prompt {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.console-input {
|
||||||
|
color: #ffcc00;
|
||||||
|
background-color: #35291c;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-button {
|
||||||
|
transform: rotate(-25deg) translateY(20px) translateX(-20px);
|
||||||
|
margin: 2px;
|
||||||
|
width: 50px;
|
||||||
|
background-color: #7e777b;
|
||||||
|
border: 1px solid dimgray;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: whitesmoke;
|
||||||
|
box-shadow: rgb(0, 0, 0, .2) 2px 2px 1px 1px;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user