generated from rnentjes/kotlin-server-web-undertow
Compare commits
2 Commits
c74c0134ac
...
c7552c2a95
| Author | SHA1 | Date | |
|---|---|---|---|
| c7552c2a95 | |||
| 63f9a1f928 |
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -3,7 +3,7 @@
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<file type="web" url="file://$PROJECT_DIR$" />
|
||||
</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" />
|
||||
</component>
|
||||
<component name="accountSettings">
|
||||
|
||||
107
build.gradle.kts
107
build.gradle.kts
@@ -1,70 +1,75 @@
|
||||
@file:OptIn(ExperimentalDistributionDsl::class)
|
||||
|
||||
import org.codehaus.groovy.tools.shell.util.Logger.io
|
||||
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalDistributionDsl
|
||||
|
||||
plugins {
|
||||
kotlin("multiplatform") version "2.1.20"
|
||||
kotlin("multiplatform") version "2.1.20"
|
||||
}
|
||||
|
||||
group = "nl.astraeus"
|
||||
version = "0.1.0-SNAPSHOT"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url = uri("https://gitea.astraeus.nl/api/packages/rnentjes/maven")
|
||||
}
|
||||
maven {
|
||||
url = uri("https://gitea.astraeus.nl:8443/api/packages/rnentjes/maven")
|
||||
}
|
||||
mavenCentral()
|
||||
maven {
|
||||
url = uri("https://gitea.astraeus.nl/api/packages/rnentjes/maven")
|
||||
}
|
||||
maven {
|
||||
url = uri("https://gitea.astraeus.nl:8443/api/packages/rnentjes/maven")
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(17)
|
||||
jvm()
|
||||
js {
|
||||
binaries.executable()
|
||||
browser {
|
||||
distribution {
|
||||
outputDirectory.set(File("$projectDir/web/"))
|
||||
}
|
||||
}
|
||||
jvmToolchain(21)
|
||||
jvm()
|
||||
js {
|
||||
binaries.executable()
|
||||
browser {
|
||||
distribution {
|
||||
outputDirectory.set(File("$projectDir/web/"))
|
||||
}
|
||||
}
|
||||
sourceSets {
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
api("nl.astraeus:kotlin-simple-logging:1.1.1")
|
||||
api("nl.astraeus:kotlin-css-generator:1.0.10")
|
||||
}
|
||||
|
||||
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")
|
||||
// Add this to enable ExperimentalStdlibApi globally
|
||||
sourceSets.all {
|
||||
languageSettings.optIn("kotlin.ExperimentalStdlibApi")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
sourceSets {
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
api("nl.astraeus:kotlin-simple-logging:1.1.1")
|
||||
api("nl.astraeus:kotlin-css-generator:1.0.10")
|
||||
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,8 @@ import mtmc.tokenizer.MTMCToken
|
||||
|
||||
abstract class ASMElement(
|
||||
val labels: List<MTMCToken>,
|
||||
@JvmField var lineNumber: Int
|
||||
var lineNumber: Int
|
||||
) : HasLocation {
|
||||
@JvmField
|
||||
var errors: MutableList<ASMError> = ArrayList()
|
||||
override var location: Int = 0
|
||||
override var sizeInBytes: Int = 0
|
||||
@@ -2,10 +2,9 @@ package mtmc.asm
|
||||
|
||||
import mtmc.tokenizer.MTMCToken
|
||||
|
||||
@JvmRecord
|
||||
data class ASMError(
|
||||
@JvmField val token: MTMCToken,
|
||||
@JvmField val error: String
|
||||
val token: MTMCToken,
|
||||
val error: String
|
||||
) {
|
||||
fun formattedErrorMessage(): String {
|
||||
return "Line " + token.line + ": " + error
|
||||
@@ -25,12 +25,16 @@ import mtmc.emulator.Register.Companion.isReadable
|
||||
import mtmc.emulator.Register.Companion.isWriteable
|
||||
import mtmc.os.SysCall
|
||||
import mtmc.os.exec.Executable
|
||||
import mtmc.os.fs.File
|
||||
import mtmc.tokenizer.MTMCToken
|
||||
import mtmc.tokenizer.MTMCTokenizer
|
||||
import java.io.File
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.*
|
||||
import java.util.stream.Collectors
|
||||
|
||||
fun MutableList<MTMCToken>.poll() = this.removeFirst()
|
||||
fun MutableList<MTMCToken>.peekFirst() = if (this.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
this[0]
|
||||
}
|
||||
|
||||
class Assembler {
|
||||
var instructions: MutableList<Instruction> = ArrayList<Instruction>()
|
||||
@@ -88,7 +92,7 @@ class Assembler {
|
||||
|
||||
var location = 0
|
||||
var originalLineNumber = 0
|
||||
val currentLocals: MutableMap<String?, LocalInfo?> = TreeMap<String?, LocalInfo?>()
|
||||
val currentLocals: MutableMap<String?, LocalInfo?> = mutableMapOf()
|
||||
for (instruction in instructions) {
|
||||
val asmLineNumber = instruction.lineNumber
|
||||
if (instruction is MetaInstruction) {
|
||||
@@ -145,10 +149,8 @@ class Assembler {
|
||||
val result = assemble(srcName, asm)
|
||||
if (!result.errors.isEmpty()) {
|
||||
throw RuntimeException(
|
||||
"Errors:\n" + result.errors
|
||||
.stream()
|
||||
.map<String> { e: ASMError? -> " - " + e!!.formattedErrorMessage() }
|
||||
.collect(Collectors.joining("\n")))
|
||||
"Errors:\n" + result.errors.joinToString("\n") { e: ASMError? -> " - " + e!!.formattedErrorMessage() }
|
||||
)
|
||||
}
|
||||
|
||||
return Executable(
|
||||
@@ -261,7 +263,10 @@ class Assembler {
|
||||
for (labelToken in labelTokens) {
|
||||
val labelData = Data(labelTokens, labelToken.line)
|
||||
if (hasLabel(labelToken.stringValue)) {
|
||||
labelData.addError(tokens.poll(), "Label already defined: " + labelToken.stringValue)
|
||||
labelData.addError(
|
||||
tokens.removeFirst(),
|
||||
"Label already defined: " + labelToken.stringValue
|
||||
)
|
||||
} else {
|
||||
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) {
|
||||
tokens.removeFirst()
|
||||
val metaInstruction = MetaInstruction(tokens.removeFirst())
|
||||
@@ -306,26 +311,26 @@ class Assembler {
|
||||
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(
|
||||
0
|
||||
).end == tokens.get(1).start && (tokens.get(1).stringValue == "data" ||
|
||||
tokens.get(1).stringValue == "text")
|
||||
) {
|
||||
return ASMMode.valueOf(tokens.get(1).stringValue.uppercase(Locale.getDefault()))
|
||||
return ASMMode.valueOf(tokens.get(1).stringValue.uppercase())
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun parseData(tokens: LinkedList<MTMCToken>, labelTokens: MutableList<MTMCToken>) {
|
||||
private fun parseData(tokens: MutableList<MTMCToken>, labelTokens: MutableList<MTMCToken>) {
|
||||
lastLabels = mutableListOf()
|
||||
var dataToken = tokens.poll()
|
||||
var dataToken = tokens.removeFirst()
|
||||
var dataElt = Data(labelTokens, if (dataToken == null) 0 else dataToken.line)
|
||||
if (dataToken != null) {
|
||||
if (dataToken.type == MTMCToken.TokenType.STRING) {
|
||||
val stringBytes = dataToken.stringValue.toByteArray(StandardCharsets.US_ASCII)
|
||||
val stringBytes = dataToken.stringValue.encodeToByteArray()
|
||||
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()
|
||||
dataElt.setValue(dataToken, nullTerminated)
|
||||
} else if (isInteger(dataToken)) {
|
||||
@@ -338,7 +343,7 @@ class Assembler {
|
||||
byteArrayOf((integerValue ushr 8).toByte(), integerValue.toByte())
|
||||
)
|
||||
} else if (dataToken.type == MTMCToken.TokenType.DOT) {
|
||||
dataToken = tokens.poll()
|
||||
dataToken = tokens.removeFirst()
|
||||
dataElt = Data(labelTokens, dataToken.line)
|
||||
if (dataToken.stringValue == "int") {
|
||||
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")
|
||||
}
|
||||
} 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)) {
|
||||
dataElt.addError(dataToken, "Number is too negative")
|
||||
} else {
|
||||
@@ -380,7 +385,7 @@ class Assembler {
|
||||
|
||||
for (labelToken in labelTokens) {
|
||||
if (hasLabel(labelToken.stringValue)) {
|
||||
dataElt.addError(tokens.poll(), "Label already defined: " + labelToken.stringValue)
|
||||
dataElt.addError(tokens.removeFirst(), "Label already defined: " + labelToken.stringValue)
|
||||
} else {
|
||||
labels.put(labelToken.labelValue(), dataElt)
|
||||
}
|
||||
@@ -405,9 +410,16 @@ class Assembler {
|
||||
return graphic
|
||||
}
|
||||
|
||||
private fun parseInstruction(tokens: LinkedList<MTMCToken>, labelTokens: MutableList<MTMCToken>) {
|
||||
private fun parseInstruction(
|
||||
tokens: MutableList<MTMCToken>,
|
||||
labelTokens: MutableList<MTMCToken>
|
||||
) {
|
||||
var tokens = tokens
|
||||
var instructionToken = tokens.peekFirst()
|
||||
var instructionToken = if (tokens.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
tokens[0]
|
||||
}
|
||||
if (instructionToken == null) return
|
||||
|
||||
lastLabels = mutableListOf()
|
||||
@@ -510,7 +522,7 @@ class Assembler {
|
||||
}
|
||||
|
||||
// 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)
|
||||
stackInst.setStackRegister(stackReg)
|
||||
}
|
||||
@@ -550,7 +562,7 @@ class Assembler {
|
||||
loadInst.setPointerToken(pointerReg)
|
||||
|
||||
// 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)
|
||||
loadInst.setOffsetToken(offsetReg)
|
||||
}
|
||||
@@ -598,7 +610,7 @@ class Assembler {
|
||||
//===================================================
|
||||
// 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()
|
||||
if (sysCallType == null) {
|
||||
inst.addError("Syscall required")
|
||||
@@ -610,7 +622,7 @@ class Assembler {
|
||||
return sysCallType!!
|
||||
}
|
||||
|
||||
private fun requireAluOp(tokens: LinkedList<MTMCToken>, inst: Instruction): MTMCToken {
|
||||
private fun requireAluOp(tokens: MutableList<MTMCToken>, inst: Instruction): MTMCToken {
|
||||
val sysCallType = tokens.poll()
|
||||
if (sysCallType == null) {
|
||||
inst.addError("Syscall required")
|
||||
@@ -623,7 +635,7 @@ class Assembler {
|
||||
}
|
||||
|
||||
private fun requireWriteableRegister(
|
||||
tokens: LinkedList<MTMCToken>,
|
||||
tokens: MutableList<MTMCToken>,
|
||||
instruction: Instruction
|
||||
): MTMCToken {
|
||||
val nextToken = tokens.poll()
|
||||
@@ -638,7 +650,7 @@ class Assembler {
|
||||
}
|
||||
|
||||
private fun requireReadableRegister(
|
||||
tokens: LinkedList<MTMCToken>,
|
||||
tokens: MutableList<MTMCToken>,
|
||||
instruction: Instruction
|
||||
): MTMCToken {
|
||||
val nextToken = tokens.poll()
|
||||
@@ -652,7 +664,7 @@ class Assembler {
|
||||
return nextToken!!
|
||||
}
|
||||
|
||||
private fun requireALUOp(tokens: LinkedList<MTMCToken>, instruction: Instruction): MTMCToken {
|
||||
private fun requireALUOp(tokens: MutableList<MTMCToken>, instruction: Instruction): MTMCToken {
|
||||
val nextToken = tokens.poll()
|
||||
if (nextToken == null || nextToken.type != MTMCToken.TokenType.IDENTIFIER || !isALUOp(nextToken.stringValue)) {
|
||||
instruction.addError("ALU operation required")
|
||||
@@ -660,7 +672,7 @@ class Assembler {
|
||||
return nextToken!!
|
||||
}
|
||||
|
||||
private fun requireString(tokens: LinkedList<MTMCToken>, instruction: ASMElement): MTMCToken {
|
||||
private fun requireString(tokens: MutableList<MTMCToken>, instruction: ASMElement): MTMCToken {
|
||||
val nextToken = tokens.poll()
|
||||
if (nextToken == null || nextToken.type != MTMCToken.TokenType.STRING) {
|
||||
instruction.addError("String required")
|
||||
@@ -669,7 +681,7 @@ class Assembler {
|
||||
}
|
||||
|
||||
private fun requireIntegerToken(
|
||||
tokens: LinkedList<MTMCToken>,
|
||||
tokens: MutableList<MTMCToken>,
|
||||
inst: ASMElement,
|
||||
max: Int
|
||||
): MTMCToken {
|
||||
@@ -688,7 +700,7 @@ class Assembler {
|
||||
}
|
||||
|
||||
private fun requireToken(
|
||||
tokens: LinkedList<MTMCToken>,
|
||||
tokens: MutableList<MTMCToken>,
|
||||
type: MTMCToken.TokenType,
|
||||
inst: ASMElement
|
||||
): MTMCToken? {
|
||||
@@ -700,7 +712,7 @@ class Assembler {
|
||||
}
|
||||
|
||||
private fun requireIntegerOrLabelReferenceToken(
|
||||
tokens: LinkedList<MTMCToken>,
|
||||
tokens: MutableList<MTMCToken>,
|
||||
inst: LoadStoreInstruction
|
||||
): MTMCToken {
|
||||
val token = tokens.poll()
|
||||
@@ -713,9 +725,9 @@ class Assembler {
|
||||
}
|
||||
|
||||
|
||||
private val tokensForLine: LinkedList<MTMCToken>
|
||||
private val tokensForLine: MutableList<MTMCToken>
|
||||
get() {
|
||||
val tokens = LinkedList<MTMCToken>()
|
||||
val tokens = mutableListOf<MTMCToken>()
|
||||
if (tokenizer!!.more()) {
|
||||
val first = tokenizer!!.consume()
|
||||
tokens.add(first)
|
||||
@@ -742,51 +754,51 @@ class Assembler {
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun transformSyntheticInstructions(tokens: LinkedList<MTMCToken>): LinkedList<MTMCToken> {
|
||||
fun transformSyntheticInstructions(tokens: MutableList<MTMCToken>): MutableList<MTMCToken> {
|
||||
if (!tokens.isEmpty()) {
|
||||
val first = tokens.peekFirst()
|
||||
val first = tokens.first()
|
||||
if (first.type == MTMCToken.TokenType.IDENTIFIER) {
|
||||
val stringVal = first.stringValue
|
||||
if (stringVal.endsWith("i")) {
|
||||
val op = stringVal.substring(0, stringVal.length - 1)
|
||||
if (isALUOp(op)) {
|
||||
val syntheticImmediate = tokens.removeFirst()
|
||||
tokens.addFirst(syntheticImmediate.cloneWithVal(op))
|
||||
tokens.addFirst(syntheticImmediate.cloneWithVal("imm"))
|
||||
tokens.add(0, syntheticImmediate.cloneWithVal(op))
|
||||
tokens.add(0, syntheticImmediate.cloneWithVal("imm"))
|
||||
}
|
||||
} else if (stringVal.startsWith("s")) {
|
||||
val op = stringVal.substring(1, stringVal.length)
|
||||
if (isALUOp(op)) {
|
||||
val syntheticImmediate = tokens.removeFirst()
|
||||
tokens.addFirst(syntheticImmediate.cloneWithVal(op))
|
||||
tokens.addFirst(syntheticImmediate.cloneWithVal("sop"))
|
||||
tokens.add(0, syntheticImmediate.cloneWithVal(op))
|
||||
tokens.add(0, syntheticImmediate.cloneWithVal("sop"))
|
||||
}
|
||||
} else if (stringVal == "la") {
|
||||
val syntheticImmediate = tokens.removeFirst()
|
||||
tokens.addFirst(syntheticImmediate.cloneWithVal("li"))
|
||||
tokens.add(0, syntheticImmediate.cloneWithVal("li"))
|
||||
} else if (stringVal == "ret") {
|
||||
val syntheticImmediate = tokens.removeFirst()
|
||||
tokens.addFirst(syntheticImmediate.cloneWithVal("ra"))
|
||||
tokens.addFirst(syntheticImmediate.cloneWithVal("jr"))
|
||||
tokens.add(0, syntheticImmediate.cloneWithVal("ra"))
|
||||
tokens.add(0, syntheticImmediate.cloneWithVal("jr"))
|
||||
}
|
||||
}
|
||||
}
|
||||
return tokens
|
||||
}
|
||||
|
||||
private fun maybeGetLabels(tokens: LinkedList<MTMCToken>): MutableList<MTMCToken> {
|
||||
val labels = LinkedList<MTMCToken>()
|
||||
while (!tokens.isEmpty() && tokens.getFirst().type == MTMCToken.TokenType.LABEL) {
|
||||
val label = tokens.poll()
|
||||
private fun maybeGetLabels(tokens: MutableList<MTMCToken>): MutableList<MTMCToken> {
|
||||
val labels = mutableListOf<MTMCToken>()
|
||||
while (!tokens.isEmpty() && tokens.first().type == MTMCToken.TokenType.LABEL) {
|
||||
val label = tokens.removeFirst()
|
||||
labels.add(label!!)
|
||||
}
|
||||
return labels
|
||||
}
|
||||
|
||||
private fun maybeGetLabelReference(tokens: LinkedList<MTMCToken>): MTMCToken? {
|
||||
private fun maybeGetLabelReference(tokens: MutableList<MTMCToken>): MTMCToken? {
|
||||
var label: MTMCToken? = null
|
||||
if (tokens.getFirst().type == MTMCToken.TokenType.IDENTIFIER) {
|
||||
label = tokens.poll()
|
||||
if (tokens.first().type == MTMCToken.TokenType.IDENTIFIER) {
|
||||
label = tokens.removeFirst()
|
||||
}
|
||||
return label
|
||||
}
|
||||
@@ -2,13 +2,12 @@ package mtmc.asm
|
||||
|
||||
import mtmc.emulator.DebugInfo
|
||||
|
||||
@JvmRecord
|
||||
data class AssemblyResult(
|
||||
@JvmField val code: ByteArray,
|
||||
val code: ByteArray,
|
||||
val data: ByteArray,
|
||||
val graphics: Array<ByteArray>,
|
||||
@JvmField val debugInfo: DebugInfo?,
|
||||
@JvmField val errors: MutableList<ASMError>
|
||||
val debugInfo: DebugInfo?,
|
||||
val errors: MutableList<ASMError>
|
||||
) {
|
||||
fun printErrors(): String {
|
||||
val builder = StringBuilder("Errors:\n")
|
||||
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.tokenizer.MTMCToken
|
||||
import mtmc.util.BinaryUtils
|
||||
import java.util.Locale
|
||||
|
||||
class ALUInstruction(
|
||||
type: InstructionType,
|
||||
@@ -38,7 +37,7 @@ class ALUInstruction(
|
||||
}
|
||||
|
||||
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) {
|
||||
val opCode = ALUOp.toInteger(instructionToken.stringValue)
|
||||
@@ -62,13 +61,12 @@ class ALUInstruction(
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun disassemble(instruction: Short): String? {
|
||||
if (BinaryUtils.getBits(16, 4, instruction).toInt() == 1) {
|
||||
val builder = StringBuilder()
|
||||
val opCode = BinaryUtils.getBits(12, 4, instruction)
|
||||
val op = ALUOp.fromInt(opCode)
|
||||
val aluOp = ALUOp.valueOf(op.uppercase(Locale.getDefault()))
|
||||
val aluOp = ALUOp.valueOf(op.uppercase())
|
||||
if (aluOp == ALUOp.IMM) {
|
||||
builder.append(op).append(" ")
|
||||
builder.append(fromInteger(BinaryUtils.getBits(8, 4, instruction).toInt())).append(" ")
|
||||
@@ -1,7 +1,5 @@
|
||||
package mtmc.asm.instructions
|
||||
|
||||
import java.util.*
|
||||
|
||||
enum class ALUOp(val opCode: Int, val isUnary: Boolean) {
|
||||
ADD(0x0000, false),
|
||||
SUB(0x0001, false),
|
||||
@@ -21,20 +19,17 @@ enum class ALUOp(val opCode: Int, val isUnary: Boolean) {
|
||||
IMM(0x000F, true);
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun toInteger(instruction: String): Int {
|
||||
return valueOf(instruction.uppercase(Locale.getDefault())).opCode
|
||||
return valueOf(instruction.uppercase()).opCode
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
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 {
|
||||
try {
|
||||
val aluOp = valueOf(op.uppercase(Locale.getDefault()))
|
||||
val aluOp = valueOf(op.uppercase())
|
||||
return true
|
||||
} catch (e: IllegalArgumentException) {
|
||||
return false
|
||||
@@ -7,9 +7,9 @@ import mtmc.emulator.MonTanaMiniComputer.Companion.isDoubleWordInstruction
|
||||
import mtmc.tokenizer.MTMCToken
|
||||
|
||||
abstract class Instruction(
|
||||
@JvmField val type: InstructionType,
|
||||
val type: InstructionType,
|
||||
labels: List<MTMCToken>,
|
||||
@JvmField val instructionToken: MTMCToken
|
||||
val instructionToken: MTMCToken
|
||||
) : ASMElement(labels, instructionToken.line) {
|
||||
open fun validateLabel(assembler: Assembler) {
|
||||
// default does nothing
|
||||
@@ -25,7 +25,6 @@ abstract class Instruction(
|
||||
get() = type.sizeInBytes
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun isInstruction(cmd: String): Boolean {
|
||||
return InstructionType.fromString(cmd) != null
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
package mtmc.asm.instructions
|
||||
|
||||
import java.util.*
|
||||
|
||||
enum class InstructionType @JvmOverloads constructor(
|
||||
@JvmField val instructionClass: InstructionClass?,
|
||||
enum class InstructionType constructor(
|
||||
val instructionClass: InstructionClass?,
|
||||
val sizeInBytes: Int = 2
|
||||
) {
|
||||
SYS(InstructionClass.MISC),
|
||||
@@ -86,10 +84,9 @@ enum class InstructionType @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun fromString(string: String): InstructionType? {
|
||||
try {
|
||||
return valueOf(string.uppercase(Locale.getDefault()))
|
||||
return valueOf(string.uppercase())
|
||||
} catch (e: IllegalArgumentException) {
|
||||
return null
|
||||
}
|
||||
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?)
|
||||
}
|
||||
@@ -1,21 +1,23 @@
|
||||
package mtmc.emulator
|
||||
|
||||
import mtmc.emulator.MonTanaMiniComputer.ComputerStatus
|
||||
import mtmc.util.currentTimeMillis
|
||||
import kotlin.math.max
|
||||
|
||||
/**
|
||||
*
|
||||
* @author jbanes
|
||||
*/
|
||||
class MTMCClock
|
||||
(private val computer: MonTanaMiniComputer) {
|
||||
class MTMCClock(
|
||||
private val computer: MonTanaMiniComputer
|
||||
) {
|
||||
fun run() {
|
||||
var instructions: Long = 0
|
||||
var ips: Long = 0
|
||||
var expected: Long = 0
|
||||
var virtual: Long = 0
|
||||
|
||||
var startTime = System.currentTimeMillis()
|
||||
var startTime = currentTimeMillis()
|
||||
var deltaStart: Long
|
||||
var delta: Long
|
||||
|
||||
@@ -28,8 +30,8 @@ class MTMCClock
|
||||
pulse = (if (speed <= 0) 1000000 else max(speed / 100, 1))
|
||||
ms = (if (pulse < 10) 1000 / speed else 10)
|
||||
|
||||
deltaStart = System.currentTimeMillis()
|
||||
delta = ms - (System.currentTimeMillis() - deltaStart)
|
||||
deltaStart = currentTimeMillis()
|
||||
delta = ms - (currentTimeMillis() - deltaStart)
|
||||
|
||||
|
||||
/* We've lost more than a second. Recalibrate. */
|
||||
@@ -50,11 +52,11 @@ class MTMCClock
|
||||
instructions += computer.pulse(pulse)
|
||||
|
||||
virtual += pulse
|
||||
ips = (virtual * 1000) / max(1, System.currentTimeMillis() - startTime)
|
||||
expected = (System.currentTimeMillis() - startTime) * speed / 1000
|
||||
ips = (virtual * 1000) / max(1, currentTimeMillis() - startTime)
|
||||
expected = (currentTimeMillis() - startTime) * speed / 1000
|
||||
}
|
||||
|
||||
System.err.println("Executed " + instructions + " instructions at a rate of " + ips + " ips (speed = " + speed + ")")
|
||||
//println("Executed " + instructions + " instructions at a rate of " + ips + " ips (speed = " + speed + ")")
|
||||
}
|
||||
|
||||
fun step() {
|
||||
@@ -1,16 +1,17 @@
|
||||
package mtmc.emulator
|
||||
|
||||
import mtmc.os.fs.Console
|
||||
import mtmc.os.fs.System
|
||||
import mtmc.os.shell.Shell
|
||||
import mtmc.tokenizer.MTMCScanner
|
||||
import mtmc.tokenizer.MTMCToken
|
||||
import java.io.Console
|
||||
|
||||
class MTMCConsole(private val computer: MonTanaMiniComputer) {
|
||||
var mode: Mode = Mode.NON_INTERACTIVE
|
||||
var sysConsole: Console? = null
|
||||
|
||||
// non-interactive data
|
||||
private val output = StringBuffer()
|
||||
private val output = StringBuilder()
|
||||
private var shortValueSet = false
|
||||
private var shortValue: Short = 0
|
||||
private var stringValue: String? = null
|
||||
@@ -18,7 +19,7 @@ class MTMCConsole(private val computer: MonTanaMiniComputer) {
|
||||
// TODO invert so shell is driving and console is just IO
|
||||
fun start() {
|
||||
mode = Mode.INTERACTIVE
|
||||
sysConsole = System.console()
|
||||
sysConsole = System.console
|
||||
Shell.printShellWelcome(computer)
|
||||
while (true) {
|
||||
val cmd = sysConsole!!.readLine("mtmc > ")
|
||||
@@ -48,7 +49,7 @@ class MTMCConsole(private val computer: MonTanaMiniComputer) {
|
||||
if (mode == Mode.INTERACTIVE) {
|
||||
val tokens = MTMCScanner(sysConsole!!.readLine(), "#").tokenize()
|
||||
val token = tokens.first()
|
||||
assert(token.type === MTMCToken.TokenType.CHAR)
|
||||
check(token.type === MTMCToken.TokenType.CHAR)
|
||||
return token.charValue()
|
||||
} else {
|
||||
this.shortValueSet = false
|
||||
@@ -88,7 +89,7 @@ class MTMCConsole(private val computer: MonTanaMiniComputer) {
|
||||
val text = if (index >= 0) output.substring(0, index + 1) else ""
|
||||
|
||||
if (index >= 0) {
|
||||
output.delete(0, index + 1)
|
||||
output.removeRange(0, index + 1)
|
||||
}
|
||||
|
||||
return text
|
||||
@@ -121,7 +122,7 @@ class MTMCConsole(private val computer: MonTanaMiniComputer) {
|
||||
}
|
||||
|
||||
fun resetOutput() {
|
||||
output.delete(0, output.length)
|
||||
output.removeRange(0, output.length)
|
||||
}
|
||||
|
||||
enum class Mode {
|
||||
@@ -1,7 +1,5 @@
|
||||
package mtmc.emulator
|
||||
|
||||
import java.util.*
|
||||
|
||||
class MTMCIO {
|
||||
var value: Int = 0
|
||||
|
||||
@@ -17,12 +15,12 @@ class MTMCIO {
|
||||
}
|
||||
|
||||
fun keyPressed(key: String) {
|
||||
val button = Buttons.valueOf(key.uppercase(Locale.getDefault()))
|
||||
val button = Buttons.valueOf(key.uppercase())
|
||||
value = value or button.mask
|
||||
}
|
||||
|
||||
fun keyReleased(key: String) {
|
||||
val button = Buttons.valueOf(key.uppercase(Locale.getDefault()))
|
||||
val button = Buttons.valueOf(key.uppercase())
|
||||
value = value and button.mask.inv()
|
||||
}
|
||||
}
|
||||
@@ -4,19 +4,7 @@ import mtmc.asm.instructions.Instruction
|
||||
import mtmc.os.MTOS
|
||||
import mtmc.os.fs.FileSystem
|
||||
import mtmc.util.BinaryUtils.getBits
|
||||
import java.lang.Byte
|
||||
import java.util.*
|
||||
import java.util.function.Consumer
|
||||
import java.util.stream.IntStream
|
||||
import kotlin.Array
|
||||
import kotlin.Boolean
|
||||
import kotlin.ByteArray
|
||||
import kotlin.Int
|
||||
import kotlin.IntArray
|
||||
import kotlin.Long
|
||||
import kotlin.Short
|
||||
import kotlin.ShortArray
|
||||
import kotlin.String
|
||||
import mtmc.util.Runnable
|
||||
import kotlin.experimental.and
|
||||
import kotlin.experimental.inv
|
||||
import kotlin.experimental.or
|
||||
@@ -43,10 +31,10 @@ class MonTanaMiniComputer {
|
||||
var display: MTMCDisplay = MTMCDisplay(this)
|
||||
var clock: MTMCClock = MTMCClock(this)
|
||||
var fileSystem: FileSystem = FileSystem(this)
|
||||
var rewindSteps: LinkedList<RewindStep>? = LinkedList<RewindStep>()
|
||||
var rewindSteps = mutableListOf<RewindStep>()
|
||||
|
||||
// listeners
|
||||
private val observers: MutableList<MTMCObserver> = ArrayList<MTMCObserver>()
|
||||
private val observers = mutableListOf<MTMCObserver>()
|
||||
var debugInfo: DebugInfo? = null
|
||||
private var currentRewindStep: RewindStep? = null
|
||||
|
||||
@@ -58,13 +46,15 @@ class MonTanaMiniComputer {
|
||||
registerFile = ShortArray(Register.entries.size)
|
||||
memory = ByteArray(MEMORY_SIZE)
|
||||
breakpoints = ByteArray(MEMORY_SIZE)
|
||||
rewindSteps = null
|
||||
rewindSteps.clear()
|
||||
setRegisterValue(
|
||||
Register.SP,
|
||||
MEMORY_SIZE.toShort().toInt()
|
||||
) // default the stack pointer to the top of memory
|
||||
rewindSteps = LinkedList<RewindStep>()
|
||||
observers!!.forEach(Consumer { obj: MTMCObserver? -> obj!!.computerReset() })
|
||||
rewindSteps.clear()
|
||||
observers.forEach { obj ->
|
||||
obj.computerReset()
|
||||
}
|
||||
}
|
||||
|
||||
fun load(code: ByteArray, data: ByteArray, debugInfo: DebugInfo?) {
|
||||
@@ -79,11 +69,11 @@ class MonTanaMiniComputer {
|
||||
|
||||
|
||||
val codeBoundary = code.size
|
||||
System.arraycopy(code, 0, memory, 0, codeBoundary)
|
||||
code.copyInto(memory, 0, 0, codeBoundary)
|
||||
setRegisterValue(Register.CB, codeBoundary - 1)
|
||||
|
||||
val dataBoundary = codeBoundary + data.size
|
||||
System.arraycopy(data, 0, memory, codeBoundary, data.size)
|
||||
code.copyInto(memory, codeBoundary, 0, data.size)
|
||||
setRegisterValue(Register.DB, dataBoundary - 1)
|
||||
|
||||
|
||||
@@ -143,7 +133,9 @@ class MonTanaMiniComputer {
|
||||
|
||||
fun fetchAndExecute() {
|
||||
currentRewindStep = RewindStep()
|
||||
rewindSteps!!.push(currentRewindStep)
|
||||
currentRewindStep?.let {
|
||||
rewindSteps.add(0, it)
|
||||
}
|
||||
fetchCurrentInstruction()
|
||||
val instruction = getRegisterValue(Register.IR)
|
||||
if (isDoubleWordInstruction(instruction)) {
|
||||
@@ -158,7 +150,7 @@ class MonTanaMiniComputer {
|
||||
}
|
||||
|
||||
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)
|
||||
if (instructionType.toInt() == 0x0000) { // MISC
|
||||
val topNibble: Int = getBits(12, 4, instruction).toInt()
|
||||
@@ -710,7 +702,9 @@ class MonTanaMiniComputer {
|
||||
} else {
|
||||
badInstruction(instruction)
|
||||
}
|
||||
observers.forEach(Consumer { o: MTMCObserver? -> o!!.afterExecution(instruction) })
|
||||
observers.forEach { o: MTMCObserver? ->
|
||||
o!!.afterExecution(instruction)
|
||||
}
|
||||
}
|
||||
|
||||
val isFlagTestBitSet: Boolean
|
||||
@@ -733,7 +727,7 @@ class MonTanaMiniComputer {
|
||||
private fun badInstruction(instruction: Short) {
|
||||
setStatus(ComputerStatus.PERMANENT_ERROR)
|
||||
// TODO implement flags
|
||||
console.println("BAD INSTRUCTION: 0x" + Integer.toHexString(instruction.toInt() and 0xFFFF))
|
||||
console.println("BAD INSTRUCTION: 0x" + (instruction.toInt() and 0xFFFF).toHexString())
|
||||
}
|
||||
|
||||
fun fetchCurrentInstruction() {
|
||||
@@ -746,14 +740,16 @@ class MonTanaMiniComputer {
|
||||
} else {
|
||||
setRegisterValue(Register.DR, 0)
|
||||
}
|
||||
observers!!.forEach(Consumer { o: MTMCObserver? -> o!!.instructionFetched(instruction) })
|
||||
observers.forEach { o: MTMCObserver? ->
|
||||
o!!.instructionFetched(instruction)
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchWordFromMemory(address: Int): Short {
|
||||
val upperByte = fetchByteFromMemory(address).toShort()
|
||||
val lowerByte = fetchByteFromMemory(address + 1)
|
||||
var value = (upperByte.toInt() shl 8).toShort()
|
||||
val i = value.toInt() or Byte.toUnsignedInt(lowerByte)
|
||||
val i = value.toInt() or lowerByte.toInt()
|
||||
value = i.toShort()
|
||||
return value
|
||||
}
|
||||
@@ -762,9 +758,8 @@ class MonTanaMiniComputer {
|
||||
if (address < 0 || address >= memory.size) {
|
||||
setStatus(ComputerStatus.PERMANENT_ERROR)
|
||||
console.println(
|
||||
"BAD MEMORY LOCATION ON READ: " + address + " (0x" + Integer.toHexString(
|
||||
address and 0xFFFF
|
||||
) + ")"
|
||||
"BAD MEMORY LOCATION ON READ: $address (0x" +
|
||||
(address and 0xFFFF).toHexString() + ")"
|
||||
)
|
||||
return 0
|
||||
} else {
|
||||
@@ -782,23 +777,24 @@ class MonTanaMiniComputer {
|
||||
if (address < 0 || address >= memory.size) {
|
||||
setStatus(ComputerStatus.PERMANENT_ERROR)
|
||||
console.println(
|
||||
"BAD MEMORY LOCATION ON WRITE: " + address + " (0x" + Integer.toHexString(
|
||||
address and 0xFFFF
|
||||
) + ")"
|
||||
"BAD MEMORY LOCATION ON WRITE: " + address + " (0x" +
|
||||
(address and 0xFFFF).toHexString() + ")"
|
||||
)
|
||||
return
|
||||
}
|
||||
val currentValue = memory[address]
|
||||
addRewindStep(Runnable { memory[address] = currentValue })
|
||||
addRewindStep { memory[address] = currentValue }
|
||||
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?) {
|
||||
if (currentRewindStep != null && rewindSteps != null) {
|
||||
currentRewindStep!!.addSubStep(runnable)
|
||||
if (rewindSteps!!.size > MAX_REWIND_STEPS) {
|
||||
rewindSteps!!.removeLast()
|
||||
if (rewindSteps.size > MAX_REWIND_STEPS) {
|
||||
rewindSteps.removeLast()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -812,11 +808,13 @@ class MonTanaMiniComputer {
|
||||
// TODO mark as overflow
|
||||
}
|
||||
val currentValue = registerFile[register]
|
||||
addRewindStep(Runnable {
|
||||
addRewindStep {
|
||||
registerFile[register] = currentValue
|
||||
})
|
||||
}
|
||||
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 {
|
||||
@@ -850,7 +848,9 @@ class MonTanaMiniComputer {
|
||||
}
|
||||
|
||||
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
|
||||
@@ -858,18 +858,15 @@ class MonTanaMiniComputer {
|
||||
|
||||
val memoryAddresses: Iterable<Int?>
|
||||
get() = Iterable {
|
||||
IntStream.range(
|
||||
0,
|
||||
MEMORY_SIZE
|
||||
).iterator()
|
||||
(0..MEMORY_SIZE).iterator()
|
||||
}
|
||||
|
||||
fun addObserver(observer: MTMCObserver?) {
|
||||
observers!!.add(observer!!)
|
||||
observers.add(observer!!)
|
||||
}
|
||||
|
||||
fun removeObserver(observer: MTMCObserver?) {
|
||||
observers!!.remove(observer!!)
|
||||
observers.remove(observer!!)
|
||||
}
|
||||
|
||||
fun pause() {
|
||||
@@ -936,7 +933,7 @@ class MonTanaMiniComputer {
|
||||
fun setArg(arg: String) {
|
||||
if (!arg.isEmpty()) {
|
||||
val start = getRegisterValue(Register.BP)
|
||||
val bytes = arg.toByteArray()
|
||||
val bytes = arg.encodeToByteArray()
|
||||
writeStringToMemory(start.toInt(), bytes)
|
||||
setRegisterValue(Register.A0, start.toInt())
|
||||
setRegisterValue(Register.BP, start + bytes.size + 1)
|
||||
@@ -953,7 +950,7 @@ class MonTanaMiniComputer {
|
||||
}
|
||||
|
||||
fun rewind() {
|
||||
val latestRewindStep = rewindSteps!!.pop()
|
||||
val latestRewindStep = rewindSteps.removeFirst()
|
||||
latestRewindStep.rewind()
|
||||
}
|
||||
|
||||
@@ -1020,7 +1017,6 @@ class MonTanaMiniComputer {
|
||||
return false
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
val computer = MonTanaMiniComputer()
|
||||
computer.speed = 1 // default to 1hz
|
||||
@@ -73,4 +73,4 @@ enum class Register {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
package mtmc.emulator
|
||||
|
||||
import mtmc.util.Runnable
|
||||
|
||||
class RewindStep {
|
||||
var subSteps: MutableList<Runnable?> = ArrayList<Runnable?>()
|
||||
|
||||
fun rewind() {
|
||||
subSteps.reversed().forEach({ obj: Runnable? -> obj!!.run() })
|
||||
subSteps.reversed().forEach({ obj: Runnable? -> obj!!.invoke() })
|
||||
}
|
||||
|
||||
fun addSubStep(subStep: Runnable?) {
|
||||
@@ -3,18 +3,17 @@ package mtmc.os
|
||||
import mtmc.emulator.MonTanaMiniComputer
|
||||
import mtmc.emulator.Register
|
||||
import mtmc.os.SysCall.Companion.getValue
|
||||
import mtmc.os.fs.File
|
||||
import mtmc.os.fs.Files
|
||||
import mtmc.os.shell.Shell
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.nio.file.Files
|
||||
import java.util.*
|
||||
import mtmc.util.currentTimeMillis
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.text.Charsets.US_ASCII
|
||||
import kotlin.random.Random
|
||||
|
||||
class MTOS(private val computer: MonTanaMiniComputer) {
|
||||
private var timer: Long = 0
|
||||
var random: Random = Random()
|
||||
var random: Random = Random.Default
|
||||
|
||||
// Editor support
|
||||
var currentFile: String? = null
|
||||
@@ -98,7 +97,7 @@ class MTOS(private val computer: MonTanaMiniComputer) {
|
||||
}
|
||||
}
|
||||
val string = computer.console.readString()
|
||||
val bytes = string!!.toByteArray(US_ASCII)
|
||||
val bytes = string!!.encodeToByteArray()
|
||||
val bytesToRead = min(bytes.size, maxLen.toInt())
|
||||
for (i in 0..<bytesToRead) {
|
||||
val aByte = bytes[i]
|
||||
@@ -242,8 +241,8 @@ class MTOS(private val computer: MonTanaMiniComputer) {
|
||||
// special handling for game-of-life cell files
|
||||
if ("cells" == fileType) {
|
||||
val str = Files.readString(file.toPath())
|
||||
val lines = Arrays
|
||||
.stream<String>(str.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray())
|
||||
val lines: List<String> =
|
||||
str.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
.filter { s: String? -> !s!!.startsWith("!") }
|
||||
.toList()
|
||||
|
||||
@@ -251,7 +250,7 @@ class MTOS(private val computer: MonTanaMiniComputer) {
|
||||
val cappedLines = min(linesTotal, maxSize2.toInt())
|
||||
|
||||
for (lineNum in 0..<cappedLines) {
|
||||
val line = lines.get(lineNum)
|
||||
val line: String = lines.get(lineNum)
|
||||
for (colNum in 0..<maxSize1) {
|
||||
val offset = lineNum * maxSize1 + colNum
|
||||
val byteOffset = offset / 8
|
||||
@@ -275,7 +274,7 @@ class MTOS(private val computer: MonTanaMiniComputer) {
|
||||
}
|
||||
}
|
||||
computer.setRegisterValue(Register.RV, 0)
|
||||
} catch (e: IOException) {
|
||||
} catch (e: Exception) {
|
||||
computer.setRegisterValue(Register.RV, -1)
|
||||
e.printStackTrace() // debugging
|
||||
}
|
||||
@@ -290,7 +289,6 @@ class MTOS(private val computer: MonTanaMiniComputer) {
|
||||
computer.writeByteToMemory(destination + i, aByte)
|
||||
}
|
||||
|
||||
|
||||
//TODO: Should this return the length with or without the null terminator?
|
||||
computer.writeByteToMemory(destination + maxSize - 1, 0.toByte())
|
||||
computer.setRegisterValue(Register.RV, maxSize - 1)
|
||||
@@ -300,18 +298,18 @@ class MTOS(private val computer: MonTanaMiniComputer) {
|
||||
|
||||
if (computer.fileSystem.exists(dir)) {
|
||||
computer.setRegisterValue(Register.RV, 0)
|
||||
computer.fileSystem.setCWD(dir)
|
||||
computer.fileSystem.cWD = dir
|
||||
} else {
|
||||
computer.setRegisterValue(Register.RV, 1)
|
||||
}
|
||||
} else if (syscallNumber == getValue("timer").toShort()) {
|
||||
val value = computer.getRegisterValue(Register.A0)
|
||||
|
||||
if (value > 0) this.timer = System.currentTimeMillis() + value
|
||||
if (value > 0) this.timer = currentTimeMillis() + value
|
||||
|
||||
computer.setRegisterValue(
|
||||
Register.RV,
|
||||
max(0, this.timer - System.currentTimeMillis()).toInt()
|
||||
max(0, this.timer - currentTimeMillis()).toInt()
|
||||
)
|
||||
} else if (syscallNumber == getValue("drawimg").toShort()) {
|
||||
val image = computer.getRegisterValue(Register.A0)
|
||||
@@ -401,10 +399,13 @@ class MTOS(private val computer: MonTanaMiniComputer) {
|
||||
}
|
||||
|
||||
val file = list[offset.toInt()]
|
||||
val name = file.getName()
|
||||
val name = file.name
|
||||
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) {
|
||||
val aByte = name.get(i).code.toByte()
|
||||
@@ -433,9 +434,9 @@ class MTOS(private val computer: MonTanaMiniComputer) {
|
||||
length++
|
||||
}
|
||||
try {
|
||||
val outputString = String(computer.memory, pointer.toInt(), length.toInt(), US_ASCII)
|
||||
val outputString = computer.memory.decodeToString(pointer.toInt(), pointer.toInt() + length)
|
||||
return outputString
|
||||
} catch (ignored: StringIndexOutOfBoundsException) {
|
||||
} catch (ignored: IndexOutOfBoundsException) {
|
||||
computer.setStatus(MonTanaMiniComputer.ComputerStatus.PERMANENT_ERROR)
|
||||
return ""
|
||||
}
|
||||
@@ -447,9 +448,12 @@ class MTOS(private val computer: MonTanaMiniComputer) {
|
||||
}
|
||||
}
|
||||
|
||||
fun loadFile(path: String?): File {
|
||||
val fs = computer.fileSystem
|
||||
val file = fs.getRealPath(path).toFile()
|
||||
return file
|
||||
}
|
||||
fun loadFile(path: String?): File = File(path ?: "<empty>")
|
||||
/*
|
||||
{
|
||||
val fs = computer.fileSystem
|
||||
val file = fs.getRealPath(path).toFile()
|
||||
return file
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package mtmc.os
|
||||
|
||||
import java.util.*
|
||||
|
||||
enum class SysCall(value: Int) {
|
||||
EXIT(0x00),
|
||||
RINT(0x01),
|
||||
@@ -50,25 +48,24 @@ enum class SysCall(value: Int) {
|
||||
companion object {
|
||||
fun isSysCall(call: String): Boolean {
|
||||
try {
|
||||
valueOf(call.uppercase(Locale.getDefault()))
|
||||
valueOf(call.uppercase())
|
||||
return true
|
||||
} catch (e: IllegalArgumentException) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getValue(call: String): Byte {
|
||||
return valueOf(call.uppercase(Locale.getDefault())).value
|
||||
return valueOf(call.uppercase()).value
|
||||
}
|
||||
|
||||
fun getString(syscallCode: Byte): String? {
|
||||
for (o in entries) {
|
||||
if (o.value == syscallCode) {
|
||||
return o.name.lowercase(Locale.getDefault())
|
||||
return o.name.lowercase()
|
||||
}
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
47
src/commonMain/kotlin/mtmc/os/fs/File.kt
Normal file
47
src/commonMain/kotlin/mtmc/os/fs/File.kt
Normal file
@@ -0,0 +1,47 @@
|
||||
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 = TODO("initialize me")
|
||||
|
||||
constructor(name: String) : this(null, name)
|
||||
|
||||
fun getParent(): File = parent ?: error("No parent")
|
||||
|
||||
fun getPath(): String = if (parent == null) {
|
||||
name
|
||||
} else {
|
||||
"${parent.getPath()}/$name"
|
||||
}
|
||||
|
||||
fun exists(): Boolean {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
15
src/commonMain/kotlin/mtmc/os/fs/Path.kt
Normal file
15
src/commonMain/kotlin/mtmc/os/fs/Path.kt
Normal file
@@ -0,0 +1,15 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
163
src/commonMain/kotlin/mtmc/os/shell/Shell.kt
Normal file
163
src/commonMain/kotlin/mtmc/os/shell/Shell.kt
Normal file
@@ -0,0 +1,163 @@
|
||||
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) {
|
||||
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 {
|
||||
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.MonTanaMiniComputer
|
||||
import mtmc.os.fs.File
|
||||
import mtmc.os.shell.ShellCommand
|
||||
import mtmc.tokenizer.MTMCToken
|
||||
import mtmc.tokenizer.MTMCTokenizer
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import javax.imageio.ImageIO
|
||||
import kotlin.random.Random
|
||||
|
||||
class DisplayCommand : ShellCommand() {
|
||||
var random: Random = Random()
|
||||
var random: Random = Random.Default
|
||||
|
||||
@Throws(Exception::class)
|
||||
public override fun exec(tokens: MTMCTokenizer, computer: MonTanaMiniComputer) {
|
||||
@@ -42,8 +41,8 @@ class DisplayCommand : ShellCommand() {
|
||||
if (tokens.more()) {
|
||||
val imagePath = tokens.collapseTokensAsString()
|
||||
val file = computer.oS.loadFile(imagePath)
|
||||
val img = ImageIO.read(file)
|
||||
computer.display.loadScaledImage(img)
|
||||
//val img = ImageIO.read(file)
|
||||
//computer.display.loadScaledImage(img)
|
||||
} else {
|
||||
usageException()
|
||||
}
|
||||
@@ -54,11 +53,15 @@ class DisplayCommand : ShellCommand() {
|
||||
} else if (tokens.match(MTMCToken.TokenType.INTEGER)) {
|
||||
val row = tokens.consumeAsInteger()
|
||||
val col = tokens.require(
|
||||
mtmc.tokenizer.MTMCToken.TokenType.INTEGER,
|
||||
java.lang.Runnable { this.usageException() })!!.intValue()
|
||||
mtmc.tokenizer.MTMCToken.TokenType.INTEGER
|
||||
) {
|
||||
this.usageException()
|
||||
}!!.intValue()
|
||||
val color = tokens.require(
|
||||
mtmc.tokenizer.MTMCToken.TokenType.INTEGER,
|
||||
java.lang.Runnable { this.usageException() })!!.intValue()
|
||||
mtmc.tokenizer.MTMCToken.TokenType.INTEGER
|
||||
) {
|
||||
this.usageException()
|
||||
}!!.intValue()
|
||||
computer.display.setPixel(row, col, color)
|
||||
} else {
|
||||
usageException()
|
||||
@@ -7,7 +7,7 @@ import mtmc.tokenizer.MTMCTokenizer
|
||||
class ExitCommand : ShellCommand() {
|
||||
public override fun exec(tokens: MTMCTokenizer, computer: MonTanaMiniComputer) {
|
||||
computer.console.println("Goodbye!")
|
||||
System.exit(1)
|
||||
//System.exit(1)
|
||||
}
|
||||
|
||||
override val help: String
|
||||
@@ -3,13 +3,13 @@ package mtmc.os.shell.builtins
|
||||
import mtmc.emulator.MonTanaMiniComputer
|
||||
import mtmc.os.exec.Executable.Companion.load
|
||||
import mtmc.os.fs.FileSystem
|
||||
import mtmc.os.fs.Path
|
||||
import mtmc.os.shell.ShellCommand
|
||||
import mtmc.tokenizer.MTMCTokenizer
|
||||
import java.nio.file.Path
|
||||
|
||||
class LoadCommand : ShellCommand() {
|
||||
@Throws(Exception::class)
|
||||
public override fun exec(tokens: MTMCTokenizer, computer: MonTanaMiniComputer) {
|
||||
override fun exec(tokens: MTMCTokenizer, computer: MonTanaMiniComputer) {
|
||||
val fs = computer.fileSystem
|
||||
val program = tokens.collapseTokensAsString()
|
||||
require(!(program == null || program.isBlank())) { "missing or required argument 'src'" }
|
||||
@@ -1,10 +1,8 @@
|
||||
package mtmc.os.shell.builtins
|
||||
|
||||
import mtmc.emulator.MonTanaMiniComputer
|
||||
import mtmc.lang.sea.SeaLanguage
|
||||
import mtmc.os.shell.ShellCommand
|
||||
import mtmc.tokenizer.MTMCTokenizer
|
||||
import mtmc.util.StringEscapeUtils.escapeString
|
||||
|
||||
class SeacCommand : ShellCommand() {
|
||||
@Throws(Exception::class)
|
||||
@@ -24,15 +22,18 @@ class SeacCommand : ShellCommand() {
|
||||
|
||||
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))
|
||||
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)
|
||||
computer.notifyOfFileSystemUpdate()
|
||||
/*
|
||||
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)
|
||||
computer.notifyOfFileSystemUpdate()
|
||||
*/
|
||||
}
|
||||
|
||||
override val help: String
|
||||
@@ -40,7 +40,10 @@ class SetCommand : ShellCommand() {
|
||||
if (value?.type === MTMCToken.TokenType.INTEGER || value?.type === MTMCToken.TokenType.BINARY || value?.type === MTMCToken.TokenType.HEX) {
|
||||
computer.writeWordToMemory(memLocation.intValue(), value.intValue())
|
||||
} 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.tokenizer.MTMCToken
|
||||
import mtmc.tokenizer.MTMCTokenizer
|
||||
import java.util.*
|
||||
|
||||
class SpeedCommand : ShellCommand() {
|
||||
private val speeds: MutableList<Int?> = Arrays.asList<Int?>(
|
||||
*arrayOf<Int>(
|
||||
private val speeds = listOf(
|
||||
1, 10, 100, 1000, 10000, 100000, 1000000
|
||||
)
|
||||
)
|
||||
|
||||
@Throws(Exception::class)
|
||||
@@ -1,14 +1,16 @@
|
||||
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) {
|
||||
var position: Int = 0
|
||||
var line: Int = 1
|
||||
var lineOffset: Int = 0
|
||||
var tokens: LinkedList<MTMCToken> = LinkedList<MTMCToken>()
|
||||
var tokens = mutableListOf<MTMCToken>()
|
||||
|
||||
fun tokenize(): LinkedList<MTMCToken> {
|
||||
fun tokenize(): MutableList<MTMCToken> {
|
||||
consumeWhitespace()
|
||||
while (!scanEnd()) {
|
||||
scanToken()
|
||||
@@ -38,7 +40,7 @@ class MTMCScanner(private val src: String, private val lineCommentStart: String)
|
||||
}
|
||||
|
||||
private fun scanLineComment(): Boolean {
|
||||
val bytes = lineCommentStart.toByteArray()
|
||||
val bytes = lineCommentStart.encodeToByteArray()
|
||||
for (i in bytes.indices) {
|
||||
val aChar = bytes[i]
|
||||
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 {
|
||||
takeChar() // take leading zero
|
||||
takeChar() // take 'x'
|
||||
while (HexFormat.isHexDigit(peek().code)) {
|
||||
while (peek().isHexDigit()) {
|
||||
takeChar()
|
||||
}
|
||||
tokens.add(makeToken(MTMCToken.TokenType.HEX, strValueFrom(start), start))
|
||||
@@ -1,13 +1,12 @@
|
||||
package mtmc.tokenizer
|
||||
|
||||
@JvmRecord
|
||||
data class MTMCToken(
|
||||
@JvmField val start: Int,
|
||||
@JvmField val end: Int,
|
||||
val start: Int,
|
||||
val end: Int,
|
||||
val line: Int,
|
||||
val lineOffset: Int,
|
||||
@JvmField val stringValue: String,
|
||||
@JvmField val type: TokenType?
|
||||
val stringValue: String,
|
||||
val type: TokenType?
|
||||
) {
|
||||
override fun toString(): String {
|
||||
return stringValue
|
||||
@@ -1,11 +1,10 @@
|
||||
package mtmc.tokenizer
|
||||
|
||||
import java.util.*
|
||||
import java.util.stream.Stream
|
||||
import mtmc.util.Runnable
|
||||
import kotlin.math.max
|
||||
|
||||
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
|
||||
|
||||
fun currentToken(): MTMCToken {
|
||||
@@ -59,10 +58,6 @@ class MTMCTokenizer(var source: String, lineCommentStart: String) {
|
||||
return tokens.get(max(0, currentToken - 1))
|
||||
}
|
||||
|
||||
fun stream(): Stream<MTMCToken?> {
|
||||
return tokens.stream()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
val sb = StringBuilder()
|
||||
for (i in tokens.indices) {
|
||||
@@ -104,7 +99,7 @@ class MTMCTokenizer(var source: String, lineCommentStart: String) {
|
||||
if (match(tokenType)) {
|
||||
return consume()
|
||||
} else {
|
||||
notFound.run()
|
||||
notFound.invoke()
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -22,27 +22,21 @@ object BinaryUtils {
|
||||
}
|
||||
|
||||
fun toBinary(aByte: Byte): String {
|
||||
val binaryString = Integer.toBinaryString(aByte.toInt())
|
||||
val formatted = String.format("%8s", binaryString)
|
||||
val zeroed = formatted.replace(" ".toRegex(), "0")
|
||||
val zeroed = aByte.toInt().toString(2).padStart(8, '0')
|
||||
val underScored = zeroed.replace("....".toRegex(), "$0_")
|
||||
val noTrailingUnderscore = underScored.substring(0, underScored.length - 1)
|
||||
return "0b" + noTrailingUnderscore
|
||||
}
|
||||
|
||||
fun toBinary(aShort: Short): String {
|
||||
val binaryString = Integer.toBinaryString(aShort.toInt())
|
||||
val formatted = String.format("%16s", binaryString)
|
||||
val zeroed = formatted.replace(" ".toRegex(), "0")
|
||||
val zeroed = aShort.toInt().toString(2).padStart(16, '0')
|
||||
val underScored = zeroed.replace("....".toRegex(), "$0_")
|
||||
val noTrailingUnderscore = underScored.substring(0, underScored.length - 1)
|
||||
return "0b" + noTrailingUnderscore
|
||||
}
|
||||
|
||||
fun toBinary(anInt: Int): String {
|
||||
val binaryString = Integer.toBinaryString(anInt)
|
||||
val formatted = String.format("%32s", binaryString)
|
||||
val zeroed = formatted.replace(" ".toRegex(), "0")
|
||||
val zeroed = anInt.toString(2).padStart(32, '0')
|
||||
val underScored = zeroed.replace("....".toRegex(), "$0_")
|
||||
val noTrailingUnderscore = underScored.substring(0, underScored.length - 1)
|
||||
return "0b" + noTrailingUnderscore
|
||||
3
src/commonMain/kotlin/mtmc/util/PlatformSpecific.kt
Normal file
3
src/commonMain/kotlin/mtmc/util/PlatformSpecific.kt
Normal file
@@ -0,0 +1,3 @@
|
||||
package mtmc.util
|
||||
|
||||
expect fun currentTimeMillis(): Long
|
||||
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
|
||||
5
src/jsMain/kotlin/mtmc/util/PlatformSpecific.js.kt
Normal file
5
src/jsMain/kotlin/mtmc/util/PlatformSpecific.js.kt
Normal file
@@ -0,0 +1,5 @@
|
||||
package mtmc.util
|
||||
|
||||
import kotlin.js.Date
|
||||
|
||||
actual fun currentTimeMillis(): Long = Date().getTime().toLong()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user