Remove legacy JVM-specific file system, shell, and related implementations; migrate to platform-agnostic and common main modules.

This commit is contained in:
2025-08-14 16:04:13 +02:00
parent 63f9a1f928
commit c7552c2a95
133 changed files with 981 additions and 898 deletions

2
.idea/misc.xml generated
View File

@@ -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">

View File

@@ -1,70 +1,75 @@
@file:OptIn(ExperimentalDistributionDsl::class) @file:OptIn(ExperimentalDistributionDsl::class)
import org.codehaus.groovy.tools.shell.util.Logger.io
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 { js {
binaries.executable() binaries.executable()
browser { browser {
distribution { distribution {
outputDirectory.set(File("$projectDir/web/")) 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") // Add this to enable ExperimentalStdlibApi globally
} sourceSets.all {
} languageSettings.optIn("kotlin.ExperimentalStdlibApi")
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") sourceSets {
implementation("com.zaxxer:HikariCP:4.0.3") val commonMain by getting {
implementation("nl.astraeus:simple-jdbc-stats:1.6.1") { dependencies {
exclude(group = "org.slf4j", module = "slf4j-api") 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("org.jetbrains.kotlinx:kotlinx-datetime:0.6.0")
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
} }
} 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
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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)
} }
@@ -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,9 +725,9 @@ 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)
@@ -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
} }

View File

@@ -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")

View 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) {}
}

View File

@@ -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(" ")

View File

@@ -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

View File

@@ -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
} }

View File

@@ -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
} }

View 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?)
}

View File

@@ -1,21 +1,23 @@
package mtmc.emulator package mtmc.emulator
import mtmc.emulator.MonTanaMiniComputer.ComputerStatus import mtmc.emulator.MonTanaMiniComputer.ComputerStatus
import mtmc.util.currentTimeMillis
import kotlin.math.max import kotlin.math.max
/** /**
* *
* @author jbanes * @author jbanes
*/ */
class MTMCClock class MTMCClock(
(private val computer: MonTanaMiniComputer) { private val computer: MonTanaMiniComputer
) {
fun run() { fun run() {
var instructions: Long = 0 var instructions: Long = 0
var ips: Long = 0 var ips: Long = 0
var expected: Long = 0 var expected: Long = 0
var virtual: Long = 0 var virtual: Long = 0
var startTime = System.currentTimeMillis() var startTime = currentTimeMillis()
var deltaStart: Long var deltaStart: Long
var delta: Long var delta: Long
@@ -28,8 +30,8 @@ class MTMCClock
pulse = (if (speed <= 0) 1000000 else max(speed / 100, 1)) pulse = (if (speed <= 0) 1000000 else max(speed / 100, 1))
ms = (if (pulse < 10) 1000 / speed else 10) ms = (if (pulse < 10) 1000 / speed else 10)
deltaStart = System.currentTimeMillis() deltaStart = currentTimeMillis()
delta = ms - (System.currentTimeMillis() - deltaStart) delta = ms - (currentTimeMillis() - deltaStart)
/* We've lost more than a second. Recalibrate. */ /* We've lost more than a second. Recalibrate. */
@@ -50,11 +52,11 @@ class MTMCClock
instructions += computer.pulse(pulse) instructions += computer.pulse(pulse)
virtual += pulse virtual += pulse
ips = (virtual * 1000) / max(1, System.currentTimeMillis() - startTime) ips = (virtual * 1000) / max(1, currentTimeMillis() - startTime)
expected = (System.currentTimeMillis() - startTime) * speed / 1000 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() { fun step() {

View File

@@ -1,16 +1,17 @@
package mtmc.emulator package mtmc.emulator
import mtmc.os.fs.Console
import mtmc.os.fs.System
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.NON_INTERACTIVE
var sysConsole: Console? = null var sysConsole: Console? = null
// non-interactive data // non-interactive data
private val output = StringBuffer() private val 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,7 +19,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() sysConsole = System.console
Shell.printShellWelcome(computer) Shell.printShellWelcome(computer)
while (true) { while (true) {
val cmd = sysConsole!!.readLine("mtmc > ") val cmd = sysConsole!!.readLine("mtmc > ")
@@ -48,7 +49,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 +89,7 @@ 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) output.removeRange(0, index + 1)
} }
return text return text
@@ -121,7 +122,7 @@ class MTMCConsole(private val computer: MonTanaMiniComputer) {
} }
fun resetOutput() { fun resetOutput() {
output.delete(0, output.length) output.removeRange(0, output.length)
} }
enum class Mode { enum class Mode {

View File

@@ -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()
} }
} }

View File

@@ -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
@@ -43,10 +31,10 @@ 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 = mutableListOf<RewindStep>()
// 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,13 +46,15 @@ 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 rewindSteps.clear()
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>() rewindSteps.clear()
observers!!.forEach(Consumer { obj: MTMCObserver? -> obj!!.computerReset() }) observers.forEach { obj ->
obj.computerReset()
}
} }
fun load(code: ByteArray, data: ByteArray, debugInfo: DebugInfo?) { fun load(code: ByteArray, data: ByteArray, debugInfo: DebugInfo?) {
@@ -79,11 +69,11 @@ class MonTanaMiniComputer {
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) code.copyInto(memory, codeBoundary, 0, data.size)
setRegisterValue(Register.DB, dataBoundary - 1) setRegisterValue(Register.DB, dataBoundary - 1)
@@ -143,7 +133,9 @@ class MonTanaMiniComputer {
fun fetchAndExecute() { fun fetchAndExecute() {
currentRewindStep = RewindStep() currentRewindStep = RewindStep()
rewindSteps!!.push(currentRewindStep) currentRewindStep?.let {
rewindSteps.add(0, it)
}
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,7 @@ 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.toInt() and 0xFFFF).toHexString())
} }
fun fetchCurrentInstruction() { fun fetchCurrentInstruction() {
@@ -746,14 +740,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() or lowerByte.toInt()
value = i.toShort() value = i.toShort()
return value return value
} }
@@ -762,9 +758,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,23 +777,24 @@ 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 && rewindSteps != null) {
currentRewindStep!!.addSubStep(runnable) currentRewindStep!!.addSubStep(runnable)
if (rewindSteps!!.size > MAX_REWIND_STEPS) { if (rewindSteps.size > MAX_REWIND_STEPS) {
rewindSteps!!.removeLast() rewindSteps.removeLast()
} }
} }
} }
@@ -812,11 +808,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 {
@@ -850,7 +848,9 @@ class MonTanaMiniComputer {
} }
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 +858,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 +933,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,7 +950,7 @@ class MonTanaMiniComputer {
} }
fun rewind() { fun rewind() {
val latestRewindStep = rewindSteps!!.pop() val latestRewindStep = rewindSteps.removeFirst()
latestRewindStep.rewind() latestRewindStep.rewind()
} }
@@ -1020,7 +1017,6 @@ class MonTanaMiniComputer {
return false return false
} }
@JvmStatic
fun main(args: Array<String>) { fun main(args: Array<String>) {
val computer = MonTanaMiniComputer() val computer = MonTanaMiniComputer()
computer.speed = 1 // default to 1hz computer.speed = 1 // default to 1hz

View File

@@ -73,4 +73,4 @@ enum class Register {
} }
} }
} }
} }

View File

@@ -1,10 +1,12 @@
package mtmc.emulator package mtmc.emulator
import mtmc.util.Runnable
class RewindStep { class RewindStep {
var subSteps: MutableList<Runnable?> = ArrayList<Runnable?>() var subSteps: MutableList<Runnable?> = ArrayList<Runnable?>()
fun rewind() { fun rewind() {
subSteps.reversed().forEach({ obj: Runnable? -> obj!!.run() }) subSteps.reversed().forEach({ obj: Runnable? -> obj!!.invoke() })
} }
fun addSubStep(subStep: Runnable?) { fun addSubStep(subStep: Runnable?) {

View File

@@ -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: Long = 0
var random: Random = Random() var random: Random = Random.Default
// Editor support // Editor support
var currentFile: String? = null var currentFile: String? = null
@@ -98,7 +97,7 @@ class MTOS(private val computer: MonTanaMiniComputer) {
} }
} }
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]
@@ -242,8 +241,8 @@ class MTOS(private val computer: MonTanaMiniComputer) {
// 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 lines: List<String> =
.stream<String>(str.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) str.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
.filter { s: String? -> !s!!.startsWith("!") } .filter { s: String? -> !s!!.startsWith("!") }
.toList() .toList()
@@ -251,7 +250,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 +274,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 +289,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 +298,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, 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 +399,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 +434,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 +448,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
}
*/
} }

View 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
} }
} }
} }

View 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)
}
*/
}
}

View 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")
}
}

View 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")
}
}

View 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()
}
}

View 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
}
}

View 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")
}
}

View File

@@ -1,160 +1,163 @@
package mtmc.os.shell; package mtmc.os.shell
import mtmc.asm.Assembler; import mtmc.asm.Assembler
import mtmc.asm.AssemblyResult; import mtmc.asm.instructions.Instruction.Companion.isInstruction
import mtmc.asm.instructions.Instruction; import mtmc.asm.peekFirst
import mtmc.emulator.DebugInfo; import mtmc.emulator.MonTanaMiniComputer
import mtmc.emulator.MonTanaMiniComputer; import mtmc.emulator.Register
import mtmc.emulator.Register; import mtmc.os.fs.FileSystem
import mtmc.os.exec.Executable; import mtmc.os.shell.builtins.AssembleCommand
import mtmc.os.fs.FileSystem; import mtmc.os.shell.builtins.DisplayCommand
import mtmc.os.shell.builtins.AssembleCommand; import mtmc.os.shell.builtins.ExitCommand
import mtmc.os.shell.builtins.DisplayCommand; import mtmc.os.shell.builtins.GetCommand
import mtmc.os.shell.builtins.ExitCommand; import mtmc.os.shell.builtins.HelpCommand
import mtmc.os.shell.builtins.GetCommand; import mtmc.os.shell.builtins.LoadCommand
import mtmc.os.shell.builtins.HelpCommand; import mtmc.os.shell.builtins.PauseCommand
import mtmc.os.shell.builtins.LoadCommand; import mtmc.os.shell.builtins.RunCommand
import mtmc.os.shell.builtins.PauseCommand; import mtmc.os.shell.builtins.SeacCommand
import mtmc.os.shell.builtins.RunCommand; import mtmc.os.shell.builtins.SetCommand
import mtmc.os.shell.builtins.SeacCommand; import mtmc.os.shell.builtins.SpeedCommand
import mtmc.os.shell.builtins.SetCommand; import mtmc.os.shell.builtins.StepCommand
import mtmc.os.shell.builtins.SpeedCommand; import mtmc.os.shell.builtins.WebCommand
import mtmc.os.shell.builtins.StepCommand; import mtmc.tokenizer.MTMCToken
import mtmc.os.shell.builtins.WebCommand; import mtmc.tokenizer.MTMCTokenizer
import mtmc.tokenizer.MTMCToken;
import mtmc.tokenizer.MTMCTokenizer;
import java.nio.file.NoSuchFileException; object Shell {
import java.nio.file.Path; private val COMMANDS: MutableMap<String?, ShellCommand> = LinkedHashMap<String?, ShellCommand>()
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import static mtmc.tokenizer.MTMCToken.TokenType.IDENTIFIER; init {
import static mtmc.tokenizer.MTMCToken.TokenType.QUESTION_MARK; 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())
}
public class Shell { fun isCommand(cmd: String): Boolean {
private static final Map<String, ShellCommand> COMMANDS = new LinkedHashMap<>(); return COMMANDS.containsKey(cmd.lowercase())
}
static { private fun findExecutable(path: String?, fs: FileSystem): Boolean {
COMMANDS.put("help", new HelpCommand()); if (path == null || path == "") return false
COMMANDS.put("exit", new ExitCommand()); //if (fs.exists(path) && !fs.listFiles(path).directory) return true
COMMANDS.put("set", new SetCommand()); //if (fs.exists("/bin/" + path) && !fs.listFiles("/bin/" + path).directory) return true
COMMANDS.put("get", new GetCommand());
COMMANDS.put("web", new WebCommand());
COMMANDS.put("disp", new DisplayCommand());
COMMANDS.put("asm", new AssembleCommand());
COMMANDS.put("load", new LoadCommand());
COMMANDS.put("step", new StepCommand());
COMMANDS.put("run", new RunCommand());
COMMANDS.put("pause", new PauseCommand());
COMMANDS.put("speed", new SpeedCommand());
COMMANDS.put("sc", new SeacCommand());
}
public static boolean isCommand(String cmd) { return false
return COMMANDS.containsKey(cmd.toLowerCase()); }
}
@Throws(Exception::class)
private static boolean findExecutable(String path, FileSystem fs) { private fun runExecutable(
if (path == null || path.equals("")) return false; file: String,
if (fs.exists(path) && !fs.listFiles(path).directory) return true; command: String,
if (fs.exists("/bin/" + path) && !fs.listFiles("/bin/" + path).directory) return true; tokens: MTMCTokenizer,
computer: MonTanaMiniComputer
return false; ) {
} val fs = computer.fileSystem
/*
private static void runExecutable(String file, String command, MTMCTokenizer tokens, MonTanaMiniComputer computer) throws Exception { var srcPath = Path.of("disk/" + fs.resolve(file))
FileSystem fs = computer.getFileSystem(); if (!srcPath.toFile().exists()) {
Path srcPath = Path.of("disk/" + fs.resolve(file)); srcPath = Path.of("disk" + fs.resolve("/bin/" + file))
if(!srcPath.toFile().exists()) {
srcPath = Path.of("disk" + fs.resolve("/bin/" + file));
} }
Executable exec = Executable.load(srcPath); val exec = load(srcPath)
computer.load(exec.code, exec.data, exec.graphics, exec.debugInfo); computer.load(exec!!.code, exec.data, exec.graphics, exec.debugInfo)
tokens.consume(); tokens.consume()
String arg = command.substring(file.length()).strip(); val arg = command.substring(file.length).trim()
computer.getOS().applyBreakpoints(); computer.oS.applyBreakpoints()
computer.setArg(arg); computer.setArg(arg)
computer.run(); computer.run()
} */
}
public static void execCommand(String command, MonTanaMiniComputer computer) {
MTMCTokenizer tokens = new MTMCTokenizer(command, "#"); fun execCommand(command: String, computer: MonTanaMiniComputer) {
try { val tokens = MTMCTokenizer(command, "#")
MTMCToken identifier = tokens.matchAndConsume(IDENTIFIER); try {
String cmd; val identifier = tokens.matchAndConsume(MTMCToken.TokenType.IDENTIFIER)
if (identifier == null) { val cmd: String?
MTMCToken question = tokens.matchAndConsume(QUESTION_MARK); if (identifier == null) {
String executable = tokens.collapseTokensAsString(); val question = tokens.matchAndConsume(MTMCToken.TokenType.QUESTION_MARK)
val executable = tokens.collapseTokensAsString()
// alias ? to help
if (question != null) {
cmd = "help"; // alias ? to help
} else if (findExecutable(executable, computer.getFileSystem())) { if (question != null) {
cmd = executable; cmd = "help"
} else { } else if (findExecutable(executable, computer.fileSystem)) {
printShellHelp(computer); cmd = executable
return; } else {
} printShellHelp(computer)
} else { return
cmd = identifier.stringValue(); }
} 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)
} }
if (isCommand(cmd)) { val lower = code[1].toInt() and 0xFF
COMMANDS.get(cmd.toLowerCase()).exec(tokens, computer); val higher = code[0].toInt() and 0xFF
} else { val inst = (higher shl 8) or lower
tokens.reset(); val originalDebugInfo = computer.debugInfo
LinkedList<MTMCToken> asm = new LinkedList<>(tokens.stream().toList()); computer.debugInfo = result.debugInfo
LinkedList<MTMCToken> updatedAsm = Assembler.Companion.transformSyntheticInstructions(asm); computer.execInstruction(inst.toShort())
MTMCToken firstToken = updatedAsm.peekFirst(); computer.debugInfo = originalDebugInfo
String firstTokenStr = firstToken.stringValue(); } else {
if (!updatedAsm.isEmpty() && Instruction.isInstruction(firstTokenStr)) { computer.console.println(result.printErrors())
Assembler assembler = new Assembler(); }
AssemblyResult result = assembler.assemble(command); } else {
if (result.errors.isEmpty()) { if (findExecutable(cmd, computer.fileSystem)) {
byte[] code = result.code; runExecutable(cmd, command, tokens, computer)
if (code.length == 4) { } else {
int data = (code[2] << 8) | code[3]; printShellHelp(computer)
computer.setRegisterValue(Register.DR, data); }
}
int lower = code[1] & 0xFF;
int higher = code[0] & 0xFF;
int inst = (higher << 8) | lower;
DebugInfo originalDebugInfo = computer.getDebugInfo();
computer.setDebugInfo(result.debugInfo);
computer.execInstruction((short) inst);
computer.setDebugInfo(originalDebugInfo);
} else {
computer.getConsole().println(result.printErrors());
}
} else {
if (findExecutable(cmd, computer.getFileSystem())) {
runExecutable(cmd, command, tokens, computer);
} else {
printShellHelp(computer);
}
}
}
} catch (NoSuchFileException e) {
computer.getConsole().println("No such file: " + e.getFile());
} catch (Exception e) {
computer.getConsole().println(e.getMessage());
e.printStackTrace();
} }
}
/*
} catch (e: NoSuchFileException) {
computer.console.println("No such file: " + e.getFile())
*/
} catch (e: Exception) {
computer.console.println(e.message!!)
e.printStackTrace()
} }
}
public static void printShellHelp(MonTanaMiniComputer computer) { fun printShellHelp(computer: MonTanaMiniComputer) {
computer.getConsole().println("Shell BuiltIns: \n"); computer.console.println("Shell BuiltIns: \n")
for (ShellCommand value : COMMANDS.values()) { for (value in COMMANDS.values) {
computer.getConsole().println(value.help); computer.console.println(value.help!!)
}
computer.getConsole().println("Also: ");
computer.getConsole().println(" <asm instruction>");
computer.getConsole().println("or \n" +
" <executable>\n\n");
} }
computer.console.println("Also: ")
computer.console.println(" <asm instruction>")
computer.console.println(
"or \n" +
" <executable>\n\n"
)
}
public static void printShellWelcome(MonTanaMiniComputer computer) { fun printShellWelcome(computer: MonTanaMiniComputer) {
computer.getConsole().println("Welcome to MtOS! Type ? for help"); computer.console.println("Welcome to MtOS! Type ? for help")
} }
} }

View File

@@ -1,12 +1,7 @@
package mtmc.os.shell; package mtmc.os.shell
public class UsageException extends RuntimeException { class UsageException(
private final ShellCommand cmd; cmd: ShellCommand
public UsageException(ShellCommand shellCommand) { ) : RuntimeException(
super("Usage:\n\n" + shellCommand.help); "Usage:\n\n" + cmd.help
this.cmd = shellCommand; )
}
public ShellCommand getCmd() {
return cmd;
}
}

View File

@@ -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()
}
*/
}
}

View File

@@ -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()

View File

@@ -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

View File

@@ -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'" }

View File

@@ -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

View File

@@ -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()
)
} }
} }
} }

View File

@@ -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)

View File

@@ -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))

View File

@@ -1,13 +1,12 @@
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

View File

@@ -1,11 +1,10 @@
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 currentToken(): MTMCToken {
@@ -59,10 +58,6 @@ class MTMCTokenizer(var source: String, lineCommentStart: String) {
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) {
@@ -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
} }
} }

View File

@@ -22,27 +22,21 @@ object BinaryUtils {
} }
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

View File

@@ -0,0 +1,3 @@
package mtmc.util
expect fun currentTimeMillis(): Long

View File

@@ -0,0 +1,3 @@
package mtmc.util
typealias Runnable = () -> Unit

View 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