Compare commits

...

15 Commits

Author SHA1 Message Date
4dcfe3a6ff Refactor PlatformSpecific functions with time and setTimeout, optimize RewindStep substeps handling, update MTMCClock timing logic, enhance frame processing, and integrate updateJsDisplay for dynamic updates in Main. 2025-08-23 17:44:25 +02:00
f14f316e38 Refactor BufferedImage logic with WebGL support, update DisplayView for GPU-based rendering, transition timing functions to Double, optimize MTMCClock frame logic, and add dynamic control for update state in ControlView. 2025-08-23 15:51:47 +02:00
11b069ddc5 Refactor MTMCConsole, enhance RegisterView with dynamic flag indicators, update ConsoleView for better output handling, add JavaExec task in Gradle, and update dependencies, styles, and Gradle wrapper version. 2025-08-18 16:41:02 +02:00
d7e331728f Refactor ControlView and update mtmc.css for enhanced control styling, improved layout, and dynamic speed setting functionality. 2025-08-18 11:57:33 +02:00
8457d3a854 Refactor PlatformSpecific, update MonTanaMiniComputer and MTMCDisplay logic, add splash screen rendering with SplashData, and optimize observer calls and color initialization. 2025-08-18 11:36:42 +02:00
37691dc7fa Refactor rewind functionality with circular buffer, update BufferedImageData handling in DisplayView, enhance ConsoleView rendering updates, and integrate SnakeCode data. 2025-08-17 20:32:12 +02:00
f169dce339 Refactor MTMCClock frame handling, enhance emulator performance, integrate immediateTimeout, and optimize rendering logic for RegisterView, MemoryView, and BufferedImage. 2025-08-17 16:16:57 +02:00
6acf781324 Add DiplayControlView for enhanced display rendering, refactor BufferedImage RGB logic, and update related components and styles 2025-08-16 20:48:24 +02:00
40baff5cb0 Update ConsoleView input behavior with auto-focus, enhance console styling, and refine RegisterView rendering 2025-08-15 21:42:50 +02:00
11da7fd588 Add debug println statements in Shell.execCommand and adjust ConsoleView input handling 2025-08-15 14:31:33 +02:00
4b17ce3cf5 Add DisplayView and ConsoleView components, integrate them into MTMCView, and update styles and main initialization logic 2025-08-15 14:27:31 +02:00
d5314ce046 Add MemoryView component for memory rendering, refactor BufferedImage for RGB manipulation, and cleanup unused methods and comments in several classes. 2025-08-14 21:03:03 +02:00
9f295b2fb9 Add platform-specific requestAnimationFrame, refactor redundant string accessor methods, and introduce BufferedImage and Color classes for emulator graphics rendering. 2025-08-14 16:49:39 +02:00
c7552c2a95 Remove legacy JVM-specific file system, shell, and related implementations; migrate to platform-agnostic and common main modules. 2025-08-14 16:04:13 +02:00
63f9a1f928 Rename .java to .kt 2025-08-14 16:04:12 +02:00
158 changed files with 9098 additions and 1203 deletions

2
.idea/misc.xml generated
View File

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

7
.idea/runConfigurations/MTMC_debug.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="MTMC debug" type="JavascriptDebugType" uri="http://localhost:4001/">
<method v="2">
<option name="Gradle.BeforeRunTask" enabled="true" tasks="jsBrowserDevelopmentExecutableDistribution" externalProjectPath="$PROJECT_DIR$" vmOptions="" scriptParameters="" />
</method>
</configuration>
</component>

10
.idea/runConfigurations/MainKt.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="MainKt" type="JetRunConfigurationType" nameIsGenerated="true">
<option name="MAIN_CLASS_NAME" value="mtmc.MainKt" />
<module name="mtmc-web.jvmMain" />
<shortenClasspath name="NONE" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

13
LICENSE.txt Normal file
View File

@@ -0,0 +1,13 @@
Zero-Clause BSD
=============
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@@ -1,70 +1,81 @@
@file:OptIn(ExperimentalDistributionDsl::class)
import org.codehaus.groovy.tools.shell.util.Logger.io
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalDistributionDsl
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 {
@OptIn(ExperimentalKotlinGradlePluginApi::class)
binaries {
// Configures a JavaExec task named "runJvm" and a Gradle distribution for the "main" compilation in this target
executable {
mainClass.set("mtmc.MainKt")
}
}
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")
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
}
js {
binaries.executable()
browser {
distribution {
outputDirectory.set(File("$projectDir/web/"))
}
}
}
// Add this to enable ExperimentalStdlibApi globally
sourceSets.all {
languageSettings.optIn("kotlin.ExperimentalStdlibApi")
}
sourceSets {
val commonMain by getting {
dependencies {
api("nl.astraeus:kotlin-simple-logging:1.1.1")
}
}
val commonTest by getting
val jvmMain by getting {
dependencies {
implementation("io.undertow:undertow-core:2.3.14.Final")
implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:0.11.0")
implementation("org.xerial:sqlite-jdbc:3.32.3.2")
implementation("com.zaxxer:HikariCP:4.0.3")
implementation("nl.astraeus:simple-jdbc-stats:1.6.1") {
exclude(group = "org.slf4j", module = "slf4j-api")
}
implementation("io.pebbletemplates:pebble:3.2.3")
implementation("com.google.code.gson:gson:2.12.1")
}
}
val jvmTest by getting {
dependencies {
}
}
val jsMain by getting {
dependencies {
implementation("nl.astraeus:kotlin-komponent:1.2.8")
}
}
val jsTest by getting
}
}

View File

@@ -1,6 +1,6 @@
#Sun Apr 28 09:54:33 CEST 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

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

View File

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

View File

@@ -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)
}
@@ -395,7 +400,7 @@ class Assembler {
): Graphic {
val graphic = Graphic(labelTokens, token.line)
val filename = token.stringValue
val file = File(File(this.srcName).getParent(), filename)
val file = File(File(this.srcName).parent, filename)
val index = graphics.size
data.setValue(token, byteArrayOf(((index shr 8) and 0xFF).toByte(), (index and 0xFF).toByte()))
@@ -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,14 +725,14 @@ 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)
while (tokenizer!!.more() &&
first.line == tokenizer!!.currentToken().line
first.line == tokenizer!!.getCurrentToken().line
) {
tokens.add(tokenizer!!.consume())
}
@@ -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
}

View File

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

View File

@@ -34,4 +34,4 @@ class Data(labels: MutableList<MTMCToken>, lineNumber: Int) : ASMElement(labels,
override fun addError(err: String) {
addError(labels.last(), err)
}
}
}

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

View File

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

View File

@@ -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
}
@@ -58,7 +57,7 @@ abstract class Instruction(
if (ls != null) {
return ls
}
val jumpReg = JumpInstruction.disassemble(instruction)
val jumpReg = JumpRegisterInstruction.disassemble(instruction)
if (jumpReg != null) {
return jumpReg
}
@@ -66,7 +65,7 @@ abstract class Instruction(
if (jump != null) {
return jump
}
return "<unknown>"
return ""
}
}
}

View File

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

View File

@@ -26,10 +26,16 @@ class JumpRegisterInstruction(
companion object {
fun disassemble(instruction: Short): String? {
if (BinaryUtils.getBits(16, 5, instruction).toInt() == 9) {
if (BinaryUtils.getBits(16, 4, instruction).toInt() == 9) {
val reg = BinaryUtils.getBits(4, 4, instruction)
val sb = StringBuilder("jr")
sb.append(fromInteger(reg.toInt()))
if (reg.toInt() == 11) {
return "ret"
} else {
val sb = StringBuilder("jr")
sb.append(" ")
sb.append(fromInteger(reg.toInt()))
return sb.toString()
}
}
return null
}

View File

@@ -0,0 +1,20 @@
package mtmc.emulator
expect fun createBufferedImage(width: Int, height: Int): BufferedImage
expect fun createCanvasImage(width: Int, height: Int): BufferedImage
expect fun createGLImage(width: Int, height: Int): BufferedImage
interface BufferedImage {
val width: Int
val height: Int
fun getRGB(x: Int, y: Int): Int
fun setRGB(x: Int, y: Int, intVal: Int)
}
class Dimension(
val width: Int,
val height: Int
)

View File

@@ -0,0 +1,8 @@
package mtmc.emulator
class Color(
val r: Int,
val g: Int,
val b: Int,
) {
}

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

@@ -0,0 +1,94 @@
package mtmc.emulator
import mtmc.emulator.MonTanaMiniComputer.ComputerStatus
import mtmc.util.currentTimeMillis
import mtmc.util.requestAnimationFrame
import mtmc.util.setTimeout
import mtmc.util.time
import kotlin.math.max
import kotlin.math.min
/**
*
* @author jbanes
*/
class MTMCClock(
private val computer: MonTanaMiniComputer
) {
var instructions: Long = 0
var ips: Long = 0
var expected: Long = 0
var virtual: Long = 0
var startTime = currentTimeMillis()
var lastFrame = 0.0
var speed: Long = 0
var instructionsToRun = 0.0
var frame = 0
fun run() {
requestAnimationFrame { handleFrame() }
}
fun handleFrame() {
// figure out how many instructions to execute this 'time' duration
// maximize time so we don't hang if the emulator is too slow
val time = time()
if (lastFrame == 0.0) {
lastFrame = time
requestAnimationFrame { handleFrame() }
return
}
val delta = time - lastFrame
lastFrame = time
val timeToProcess = min(delta, 16.0)
// assume 1Hz = 1 instruction/second
if (computer.getStatus() == ComputerStatus.EXECUTING) {
speed = max(computer.speed, 0).toLong()
if (speed == 0L) {
speed = 1L
}
instructionsToRun += timeToProcess * speed / 1000.0
val pulse: Long = instructionsToRun.toLong() + 1
instructionsToRun -= pulse
val start = time()
val ir = computer.pulse(pulse)
instructions += ir
val duration = (time() - start)
val actual = ir / (delta / 1000.0)
if (frame % 100 == 0) {
println("Instructions ran: $ir (delta = ${delta.toFloat()}, timeToProcess = ${timeToProcess.toFloat()}, speed = $speed (actual=${actual.toLong()}), duration = $duration)")
}
virtual += instructions
ips = (instructions / timeToProcess).toLong()
frame++
if (duration > timeToProcess) {
setTimeout({ handleFrame() })
} else {
requestAnimationFrame { handleFrame() }
}
}
//println("Executed " + instructions + " instructions at a rate of " + ips + " ips (speed = " + speed + ")")
}
fun step() {
computer.fetchAndExecute()
computer.fetchCurrentInstruction()
computer.notifyOfStepExecution()
}
fun back() {
computer.rewind()
computer.fetchCurrentInstruction()
computer.notifyOfStepExecution()
}
}

View File

@@ -1,16 +1,16 @@
package mtmc.emulator
import mtmc.os.fs.Console
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 mode: Mode = Mode.INTERACTIVE
var sysConsole: Console? = null
// non-interactive data
private val output = StringBuffer()
private var output = StringBuilder()
private var shortValueSet = false
private var shortValue: Short = 0
private var stringValue: String? = null
@@ -18,12 +18,7 @@ class MTMCConsole(private val computer: MonTanaMiniComputer) {
// TODO invert so shell is driving and console is just IO
fun start() {
mode = Mode.INTERACTIVE
sysConsole = System.console()
Shell.printShellWelcome(computer)
while (true) {
val cmd = sysConsole!!.readLine("mtmc > ")
computer.oS.processCommand(cmd)
}
}
fun println(x: String) {
@@ -48,7 +43,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 +83,9 @@ 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)
val updated = StringBuilder()
updated.append(output.removeRange(0, index + 1))
output = updated
}
return text
@@ -121,7 +118,7 @@ class MTMCConsole(private val computer: MonTanaMiniComputer) {
}
fun resetOutput() {
output.delete(0, output.length)
output.clear()
}
enum class Mode {

View File

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

View File

@@ -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
@@ -30,7 +18,7 @@ class MonTanaMiniComputer {
var memory: ByteArray = ByteArray(MEMORY_SIZE)
var breakpoints: ByteArray = ByteArray(MEMORY_SIZE)
private var status: ComputerStatus? = ComputerStatus.READY
var speed: Int = 1000000
var speed: Int = 1
set(speed) {
field = speed
this.notifyOfExecutionUpdate()
@@ -43,10 +31,12 @@ 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 = Array(MAX_REWIND_STEPS) { RewindStep() }
var rewindIndex = -1
// listeners
private val observers: MutableList<MTMCObserver> = ArrayList<MTMCObserver>()
private val observers = mutableListOf<MTMCObserver>()
var debugInfo: DebugInfo? = null
private var currentRewindStep: RewindStep? = null
@@ -58,32 +48,33 @@ class MonTanaMiniComputer {
registerFile = ShortArray(Register.entries.size)
memory = ByteArray(MEMORY_SIZE)
breakpoints = ByteArray(MEMORY_SIZE)
rewindSteps = null
setRegisterValue(
Register.SP,
MEMORY_SIZE.toShort().toInt()
) // default the stack pointer to the top of memory
rewindSteps = LinkedList<RewindStep>()
observers!!.forEach(Consumer { obj: MTMCObserver? -> obj!!.computerReset() })
rewindIndex = -1
observers.forEach { obj ->
obj.computerReset()
}
}
fun load(code: ByteArray, data: ByteArray, debugInfo: DebugInfo?) {
load(code, data, Array(0) { ByteArray(0) }, debugInfo)
}
fun load(code: ByteArray, data: ByteArray, graphics: Array<ByteArray>, debugInfo: DebugInfo?) {
fun load(
code: ByteArray,
data: ByteArray,
graphics: Array<ByteArray> = arrayOf(),
debugInfo: DebugInfo? = null
) {
this.debugInfo = debugInfo
// reset memory
initMemory()
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)
data.copyInto(memory, codeBoundary, 0, data.size)
setRegisterValue(Register.DB, dataBoundary - 1)
@@ -142,8 +133,9 @@ class MonTanaMiniComputer {
}
fun fetchAndExecute() {
currentRewindStep = RewindStep()
rewindSteps!!.push(currentRewindStep)
rewindIndex = (rewindIndex + 1) % rewindSteps.size
rewindSteps[rewindIndex].index = 0
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,11 @@ 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.toHexString()) + " at PC " + getRegisterValue(
Register.PC
)
)
}
fun fetchCurrentInstruction() {
@@ -746,14 +744,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() + (lowerByte.toInt() and 255)
value = i.toShort()
return value
}
@@ -762,9 +762,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,24 +781,22 @@ 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) {
if (currentRewindStep != null) {
currentRewindStep!!.addSubStep(runnable)
if (rewindSteps!!.size > MAX_REWIND_STEPS) {
rewindSteps!!.removeLast()
}
}
}
@@ -812,11 +809,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 {
@@ -845,12 +844,14 @@ class MonTanaMiniComputer {
breakpoints[address] = (if (active) 1.toByte() else 0.toByte())
}
private fun start() {
fun start() {
console.start() // start the interactive console
}
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 +859,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 +934,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,12 +951,15 @@ class MonTanaMiniComputer {
}
fun rewind() {
val latestRewindStep = rewindSteps!!.pop()
latestRewindStep.rewind()
if (rewindIndex >= 0) {
val latestRewindStep = rewindSteps[rewindIndex]
rewindIndex--
latestRewindStep?.rewind()
}
}
val isBackAvailable: Boolean
get() = !rewindSteps!!.isEmpty()
get() = rewindIndex >= 0
enum class ComputerStatus {
READY,
@@ -1014,17 +1015,8 @@ class MonTanaMiniComputer {
return true
}
val isMcp = getBits(16, 8, instruction) == 5.toShort()
if (isMcp) {
return true
}
return false
}
@JvmStatic
fun main(args: Array<String>) {
val computer = MonTanaMiniComputer()
computer.speed = 1 // default to 1hz
computer.start()
return isMcp
}
}
}

View File

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

View File

@@ -0,0 +1,18 @@
package mtmc.emulator
import mtmc.util.Runnable
class RewindStep {
var subSteps: Array<Runnable> = Array(10) { {} }
var index = 0
fun rewind() {
subSteps.reversed().forEach({ obj: Runnable? -> obj!!.invoke() })
}
fun addSubStep(subStep: Runnable?) {
if (subStep != null && index < subSteps.size) {
subSteps[index++] = subStep
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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()
private var timer: Double = 0.0
var random: Random = Random.Default
// Editor support
var currentFile: String? = null
@@ -52,59 +51,59 @@ class MTOS(private val computer: MonTanaMiniComputer) {
fun handleSysCall(syscallNumber: Short) {
if (syscallNumber == getValue("exit").toShort()) {
computer.setStatus(MonTanaMiniComputer.ComputerStatus.FINISHED)
} else if (syscallNumber == getValue("rint").toShort()) {
// rint
if (!computer.console.hasShortValue()) {
computer.notifyOfRequestInteger()
}
while (!computer.console.hasShortValue() && computer.getStatus() == MonTanaMiniComputer.ComputerStatus.EXECUTING) {
try {
Thread.sleep(10)
} catch (e: InterruptedException) {
}
}
val `val` = computer.console.readInt()
computer.setRegisterValue(Register.RV, `val`.toInt())
// } else if (syscallNumber == getValue("rint").toShort()) {
// // rint
// if (!computer.console.hasShortValue()) {
// computer.notifyOfRequestInteger()
// }
// while (!computer.console.hasShortValue() && computer.getStatus() == MonTanaMiniComputer.ComputerStatus.EXECUTING) {
// try {
// Thread.sleep(10)
// } catch (e: InterruptedException) {
// }
// }
// val `val` = computer.console.readInt()
// computer.setRegisterValue(Register.RV, `val`.toInt())
} else if (syscallNumber == getValue("wint").toShort()) {
// wint
val value = computer.getRegisterValue(Register.A0)
computer.console.writeInt(value)
} else if (syscallNumber == getValue("rchr").toShort()) {
if (!computer.console.hasShortValue()) {
computer.notifyOfRequestCharacter()
}
while (!computer.console.hasShortValue() && computer.getStatus() == MonTanaMiniComputer.ComputerStatus.EXECUTING) {
try {
Thread.sleep(10)
} catch (e: InterruptedException) {
}
}
val `val` = computer.console.readChar()
computer.setRegisterValue(Register.RV, `val`.code)
// } else if (syscallNumber == getValue("rchr").toShort()) {
// if (!computer.console.hasShortValue()) {
// computer.notifyOfRequestCharacter()
// }
// while (!computer.console.hasShortValue() && computer.getStatus() == MonTanaMiniComputer.ComputerStatus.EXECUTING) {
// try {
// Thread.sleep(10)
// } catch (e: InterruptedException) {
// }
// }
// val `val` = computer.console.readChar()
// computer.setRegisterValue(Register.RV, `val`.code)
} else if (syscallNumber == getValue("wchr").toShort()) {
val value = computer.getRegisterValue(Register.A0)
computer.console.print("" + Char(value.toUShort()))
} else if (syscallNumber == getValue("rstr").toShort()) {
// rstr
val pointer = computer.getRegisterValue(Register.A0)
val maxLen = computer.getRegisterValue(Register.A1)
if (!computer.console.hasReadString()) {
computer.notifyOfRequestString()
}
while (!computer.console.hasReadString() && computer.getStatus() == MonTanaMiniComputer.ComputerStatus.EXECUTING) {
try {
Thread.sleep(10)
} catch (e: InterruptedException) {
}
}
val string = computer.console.readString()
val bytes = string!!.toByteArray(US_ASCII)
val bytesToRead = min(bytes.size, maxLen.toInt())
for (i in 0..<bytesToRead) {
val aByte = bytes[i]
computer.writeByteToMemory(pointer + i, aByte)
}
computer.setRegisterValue(Register.RV, bytesToRead)
// } else if (syscallNumber == getValue("rstr").toShort()) {
// // rstr
// val pointer = computer.getRegisterValue(Register.A0)
// val maxLen = computer.getRegisterValue(Register.A1)
// if (!computer.console.hasReadString()) {
// computer.notifyOfRequestString()
// }
// while (!computer.console.hasReadString() && computer.getStatus() == MonTanaMiniComputer.ComputerStatus.EXECUTING) {
// try {
// Thread.sleep(10)
// } catch (e: InterruptedException) {
// }
// }
// val string = computer.console.readString()
// val bytes = string!!.encodeToByteArray()
// val bytesToRead = min(bytes.size, maxLen.toInt())
// for (i in 0..<bytesToRead) {
// val aByte = bytes[i]
// computer.writeByteToMemory(pointer + i, aByte)
// }
// computer.setRegisterValue(Register.RV, bytesToRead)
} else if (syscallNumber == getValue("wstr").toShort()) {
// wstr
val pointer = computer.getRegisterValue(Register.A0)
@@ -172,14 +171,14 @@ class MTOS(private val computer: MonTanaMiniComputer) {
}
computer.setRegisterValue(Register.RV, random.nextInt(low.toInt(), high + 1))
} else if (syscallNumber == getValue("sleep").toShort()) {
// sleep
val millis = computer.getRegisterValue(Register.A0)
try {
if (millis > 0) Thread.sleep(millis.toLong())
} catch (e: InterruptedException) {
throw RuntimeException(e)
}
// } else if (syscallNumber == getValue("sleep").toShort()) {
// // sleep
// val millis = computer.getRegisterValue(Register.A0)
// try {
// if (millis > 0) Thread.sleep(millis.toLong())
// } catch (e: InterruptedException) {
// throw RuntimeException(e)
// }
} else if (syscallNumber == getValue("fbreset").toShort()) {
// fbreset
computer.display.reset()
@@ -241,9 +240,40 @@ class MTOS(private val computer: MonTanaMiniComputer) {
try {
// 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 str = Files.readString(file.toPath())
val str = """
! galaxy
! Kok's galaxy - a whirling oscillator
.......................................
.......................................
.......................................
.......................................
.......................................
.......................................
.......................................
.......................................
.......................................
.......................................
.......................................
.......................................
.......................................
...............OOOOOO.OO.....
...............OOOOOO.OO
......................OO
...............OO.....OO
...............OO.....OO
...............OO.....OO
...............OO.......
...............OO.OOOOOO
...............OO.OOOOOO.......................................
.......................................
.......................................
.......................................
.......................................
.......................................
""".trimIndent()
val lines: List<String> =
str.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
.filter { s: String? -> !s!!.startsWith("!") }
.toList()
@@ -251,7 +281,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 +305,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 +320,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 +329,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.0, this.timer - currentTimeMillis()).toInt()
)
} else if (syscallNumber == getValue("drawimg").toShort()) {
val image = computer.getRegisterValue(Register.A0)
@@ -401,10 +430,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 +465,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 +479,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
}
*/
}

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

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,43 @@
package mtmc.os.fs
object Files {
fun readString(toPath: Path): String {
TODO("Not yet implemented")
}
fun readAllBytes(toPath: Path): ByteArray {
TODO("Not yet implemented")
}
}
class File(
val parent: File? = null,
val name: String
) {
var directory: Boolean = false
constructor(name: String) : this(null, name)
fun getPath(): String = if (parent == null) {
name
} else {
"${parent.getPath()}/$name"
}
fun exists(): Boolean = true
fun getAbsolutePath(): String {
TODO("Not yet implemented")
}
fun toPath(): Path {
TODO("Not yet implemented")
}
fun listFiles(): Array<File> {
TODO("Not yet implemented")
}
}

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,25 @@
package mtmc.os.fs
class Path {
fun toFile(): File {
TODO("Not yet implemented")
}
fun probeContentType(): String? {
TODO("Not yet implemented")
}
fun resolve(filename: String): Path {
TODO("Not yet implemented")
}
fun toAbsolutePath(): Path {
TODO("Not yet implemented")
}
companion object {
fun of(string: String): Path {
TODO()
}
}
}

View File

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

View File

@@ -0,0 +1,7 @@
package mtmc.os.shell
class UsageException(
cmd: ShellCommand
) : RuntimeException(
"Usage:\n\n" + cmd.help
)

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

View File

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

View File

@@ -17,11 +17,11 @@ class GetCommand : ShellCommand() {
if (memLocation == null) {
val register = tokens.matchAndConsume(MTMCToken.TokenType.IDENTIFIER)
if (register == null) usageException()
val reg = Register.toInteger(register!!.stringValue())
val reg = Register.toInteger(register!!.stringValue)
if (reg >= 0) {
computer.console.println(register.stringValue() + ": " + computer.getRegisterValue(reg))
computer.console.println(register.stringValue + ": " + computer.getRegisterValue(reg))
} else {
throw IllegalArgumentException("Bad register: " + register.stringValue())
throw IllegalArgumentException("Bad register: " + register.stringValue)
}
} else {
if (memLocation.type === MTMCToken.TokenType.INTEGER || memLocation.type === MTMCToken.TokenType.BINARY || memLocation.type === MTMCToken.TokenType.HEX) {

View File

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

View File

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

View File

@@ -23,11 +23,11 @@ class SetCommand : ShellCommand() {
MTMCToken.TokenType.BINARY
)
if (value == null) usageException()
val reg = Register.toInteger(register!!.stringValue())
val reg = Register.toInteger(register!!.stringValue)
if (reg >= 0) {
computer.setRegisterValue(reg, value!!.intValue())
} else {
throw IllegalArgumentException("Bad register: " + register.stringValue())
throw IllegalArgumentException("Bad register: " + register.stringValue)
}
} else {
val value = tokens.matchAndConsume(
@@ -40,7 +40,10 @@ class SetCommand : ShellCommand() {
if (value?.type === MTMCToken.TokenType.INTEGER || value?.type === MTMCToken.TokenType.BINARY || value?.type === MTMCToken.TokenType.HEX) {
computer.writeWordToMemory(memLocation.intValue(), value.intValue())
} 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.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)

View File

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

View File

@@ -1,22 +1,17 @@
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
}
fun stringValue(): String {
return stringValue
}
fun charValue(): Char {
return stringValue.get(0)
}

View File

@@ -1,14 +1,13 @@
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 {
fun getCurrentToken(): MTMCToken {
return tokens.get(currentToken)
}
@@ -29,8 +28,8 @@ class MTMCTokenizer(var source: String, lineCommentStart: String) {
}
fun matchAndConsume(identifier: String?): Boolean {
if (currentToken().type == MTMCToken.TokenType.IDENTIFIER &&
currentToken().stringValue == identifier
if (getCurrentToken().type == MTMCToken.TokenType.IDENTIFIER &&
getCurrentToken().stringValue == identifier
) {
return true
} else {
@@ -40,7 +39,7 @@ class MTMCTokenizer(var source: String, lineCommentStart: String) {
fun match(vararg type: MTMCToken.TokenType?): Boolean {
for (tokenType in type) {
if (currentToken().type == tokenType) {
if (getCurrentToken().type == tokenType) {
return true
}
}
@@ -52,17 +51,13 @@ class MTMCTokenizer(var source: String, lineCommentStart: String) {
}
fun more(): Boolean {
return currentToken().type != MTMCToken.TokenType.EOF
return getCurrentToken().type != MTMCToken.TokenType.EOF
}
fun previousToken(): MTMCToken? {
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) {
@@ -88,7 +83,7 @@ class MTMCTokenizer(var source: String, lineCommentStart: String) {
do {
last = consume()
sb.append(last.stringValue)
next = currentToken()
next = getCurrentToken()
} while (more() && last.end == next.start)
return sb.toString()
} else {
@@ -104,7 +99,7 @@ class MTMCTokenizer(var source: String, lineCommentStart: String) {
if (match(tokenType)) {
return consume()
} else {
notFound.run()
notFound.invoke()
return null
}
}

View File

@@ -11,38 +11,28 @@ object BinaryUtils {
return 0
}
val returnValue = (value.toInt() and 0xffff) ushr (start - totalBits)
var mask = 0
var toShift = totalBits
while (toShift > 0) {
toShift--
mask = mask shl 1
mask = mask + 1
}
var mask = 1
mask = mask shl totalBits
mask--
return (returnValue and mask).toShort()
}
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

View File

@@ -0,0 +1,11 @@
package mtmc.util
expect fun currentTimeMillis(): Double
expect fun time(): Double
expect fun requestAnimationFrame(action: (Double) -> Unit)
expect fun setTimeout(action: () -> Unit)
expect fun immediateTimeout(action: (Double) -> Unit): Int

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,43 @@
package mtmc
import kotlinx.browser.document
import kotlinx.html.div
import nl.astraeus.komp.HtmlBuilder
import kotlinx.browser.window
import mtmc.emulator.MonTanaMiniComputer
import mtmc.util.currentTimeMillis
import mtmc.view.DisplayView
import mtmc.view.MTMCView
import nl.astraeus.komp.Komponent
class HelloKomponent : Komponent() {
override fun HtmlBuilder.render() {
div {
+ "Hello, world!"
}
}
}
val computer = MonTanaMiniComputer()
val mainView = MTMCView(computer)
val display = DisplayView(computer)
fun main() {
Komponent.create(document.body!!, HelloKomponent())
computer.speed = 1000000
computer.load(lifeCode, lifeData)
//computer.load(snakeCode, snakeData)
Komponent.create(document.body!!, mainView)
computer.start()
mainView.requestUpdate()
display.requestUpdate()
window.requestAnimationFrame { updateJsDisplay() }
}
var lastMemoryUpdate = currentTimeMillis()
var updateState = true
fun updateJsDisplay() {
display.requestUpdate()
if (currentTimeMillis() - lastMemoryUpdate > 125) {
if (updateState) {
mainView.registerView.requestUpdate()
mainView.memoryView.requestUpdate()
}
lastMemoryUpdate = currentTimeMillis()
}
window.requestAnimationFrame { updateJsDisplay() }
}

View File

@@ -0,0 +1,116 @@
package mtmc.emulator
import kotlinx.browser.document
import org.khronos.webgl.Uint8Array
import org.khronos.webgl.get
import org.khronos.webgl.set
import org.w3c.dom.CanvasRenderingContext2D
import org.w3c.dom.HTMLCanvasElement
import org.w3c.dom.ImageData
actual fun createBufferedImage(
width: Int,
height: Int
): BufferedImage = BufferedImageImpl(width, height, 1)
class BufferedImageImpl(
override val width: Int,
override val height: Int,
val type: Int
) : BufferedImage {
val display = ByteArray(width * height * 4)
override fun getRGB(x: Int, y: Int): Int {
check(x in 0 until width && y in 0 until height)
val offset = (x + y * width) * 4
return display[offset + 0].toInt() +
display[offset + 1].toInt() shl 8 +
display[offset + 2].toInt() shl 16
}
override fun setRGB(x: Int, y: Int, intVal: Int) {
check(x in 0 until width && y in 0 until height)
val offset = (x + y * width) * 4
display[offset + 0] = (intVal shr 16).toByte()
display[offset + 1] = (intVal shr 8).toByte()
display[offset + 2] = intVal.toByte()
display[offset + 3] = 255.toByte()
}
}
class BufferedImageData(
val imageData: ImageData,
) : BufferedImage {
override val width: Int = imageData.width
override val height: Int = imageData.height
override fun getRGB(x: Int, y: Int): Int {
check(x in 0 until width && y in 0 until height)
val offset = (x * 4 + y * width * 4)
val display = imageData.data
return (display[offset + 0].toInt() and 0xff) shl 16 +
(display[offset + 1].toInt() and 0xff) shl 8 +
(display[offset + 2].toInt() and 0xff)
}
override fun setRGB(x: Int, y: Int, intVal: Int) {
check(x in 0 until width && y in 0 until height)
val offset = (x * 4 + y * width * 4)
val display = imageData.data
display[offset + 0] = ((intVal shr 16) and 0xff).toShort().asDynamic()
display[offset + 1] = ((intVal shr 8) and 0xff).toShort().asDynamic()
display[offset + 2] = (intVal and 0xff).toShort().asDynamic()
display[offset + 3] = 255.toShort().asDynamic()
}
}
class BufferedImageDataWebGl(
override val width: Int,
override val height: Int,
val data: Uint8Array = Uint8Array(width * height * 4)
) : BufferedImage {
init {
for (x in 0 until width) {
for (y in 0 until height) {
val offset = (x * 4 + y * width * 4)
data[offset + 3] = 255.toShort().asDynamic()
}
}
}
override fun getRGB(x: Int, y: Int): Int {
check(x in 0 until width && y in 0 until height)
val offset = (x * 4 + y * width * 4)
val display = data
return (display[offset + 0].toInt() and 0xff) shl 16 +
(display[offset + 1].toInt() and 0xff) shl 8 +
(display[offset + 2].toInt() and 0xff) shl 0 + 255
}
override fun setRGB(x: Int, y: Int, intVal: Int) {
check(x in 0 until width && y in 0 until height)
val offset = (x * 4 + y * width * 4)
data[offset + 0] = ((intVal shr 16) and 0xff).toShort().asDynamic()
data[offset + 1] = ((intVal shr 8) and 0xff).toShort().asDynamic()
data[offset + 2] = ((intVal shr 0) and 0xff).toShort().asDynamic()
//data[offset + 3] = 255.toShort().asDynamic()
}
}
val canvas = document.createElement("canvas") as HTMLCanvasElement
val ctx = canvas.getContext("2d") as CanvasRenderingContext2D
actual fun createCanvasImage(width: Int, height: Int): BufferedImage {
return BufferedImageData(ctx.createImageData(width.toDouble(), height.toDouble()));
}
actual fun createGLImage(width: Int, height: Int): BufferedImage {
return BufferedImageDataWebGl(width, height)
}

View File

@@ -0,0 +1,19 @@
package mtmc.util
import kotlinx.browser.window
import kotlin.js.Date
actual fun currentTimeMillis(): Double = Date().getTime()
actual fun requestAnimationFrame(action: (Double) -> Unit) {
window.requestAnimationFrame {
action(it)
}
}
actual fun immediateTimeout(action: (Double) -> Unit): Int = window.setTimeout(action, 0)
actual fun time(): Double = window.performance.now()
actual fun setTimeout(action: () -> Unit) {
window.setTimeout(action)
}

View File

@@ -0,0 +1,86 @@
package mtmc.view
import kotlinx.browser.window
import kotlinx.html.div
import kotlinx.html.input
import kotlinx.html.js.onClickFunction
import kotlinx.html.js.onKeyUpFunction
import kotlinx.html.span
import mtmc.display
import mtmc.emulator.MonTanaMiniComputer
import mtmc.mainView
import mtmc.os.shell.Shell
import nl.astraeus.komp.HtmlBuilder
import nl.astraeus.komp.Komponent
import nl.astraeus.komp.currentElement
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.events.KeyboardEvent
class ConsoleView(
val computer: MonTanaMiniComputer
) : Komponent() {
var input: String = ""
var output = StringBuilder()
private var inputElement: HTMLInputElement? = null
init {
output.append(computer.console.consumeLines())
}
override fun HtmlBuilder.render() {
div("console") {
onClickFunction = {
inputElement?.focus()
}
div("console-history") {
div {
+output.toString()
}
}
div("console-input") {
span("console-prompt") {
+"mtmc$"
}
input(classes = "console-input") {
value = input
autoFocus = true
inputElement = currentElement() as? HTMLInputElement
currentElement().scrollIntoView()
window.setTimeout({
inputElement?.focus()
}, 0)
onKeyUpFunction = { it ->
(it as? KeyboardEvent)?.let { ke ->
(ke.target as? HTMLInputElement)?.let { inp ->
if (ke.key == "Enter") {
handleCommand()
} else {
input = inp.value
}
}
}
}
}
}
}
}
private fun handleCommand() {
computer.console.print("mtmc$ ")
computer.console.println(input)
Shell.execCommand(input, computer)
input = ""
output.append(computer.console.consumeLines())
if (output.length > 1000 && output.contains("\n")) {
output = output.deleteRange(0, output.indexOf("\n"))
}
mainView.registerView.requestUpdate()
mainView.memoryView.requestUpdate()
display.requestUpdate()
requestUpdate()
}
}

View File

@@ -0,0 +1,157 @@
package mtmc.view
import kotlinx.html.InputType
import kotlinx.html.button
import kotlinx.html.div
import kotlinx.html.i
import kotlinx.html.input
import kotlinx.html.js.onChangeFunction
import kotlinx.html.js.onClickFunction
import kotlinx.html.label
import kotlinx.html.option
import kotlinx.html.select
import kotlinx.html.span
import mtmc.display
import mtmc.emulator.MonTanaMiniComputer
import mtmc.mainView
import mtmc.updateState
import nl.astraeus.komp.HtmlBuilder
import nl.astraeus.komp.Komponent
import org.w3c.dom.HTMLSelectElement
import kotlin.text.Typography.nbsp
class ControlView(
val computer: MonTanaMiniComputer
) : Komponent() {
override fun HtmlBuilder.render() {
div("control-panel") {
div("control-header") {
span {
+"m"
}
span {
+"s"
}
span {
+"u"
}
nbsp
i {
+"MTMC-16"
}
}
div("control-secondary") {
+"MonTana Mini-Computer"
}
div("control-buttons") {
label {
+"Update:"
htmlFor = "update-state"
input {
type = InputType.checkBox
checked = updateState
onClickFunction = {
updateState = !updateState
requestUpdate()
}
}
}
label {
select {
name = "speed"
option {
value = "1"
+"1 Hz"
}
option {
value = "10"
+"10 Hz"
}
option {
value = "100"
+"100 Hz"
}
option {
value = "1000"
+"1 Khz"
}
option {
value = "1000000"
selected = true
+"1 Mhz"
}
option {
value = "2000000"
+"2 Mhz"
}
option {
value = "5000000"
+"5 Mhz"
}
option {
value = "10000000"
+"10 Mhz"
}
onChangeFunction = {
val target = it.target as? HTMLSelectElement
target?.value?.toIntOrNull()?.let { speed ->
computer.speed = speed
}
}
}
}
// Buttons with fx-* attributes
button {
if (computer.getStatus() != MonTanaMiniComputer.ComputerStatus.EXECUTING) {
+"run"
onClickFunction = {
computer.run()
requestUpdate()
}
} else {
+"pause"
onClickFunction = {
computer.pause()
requestUpdate()
}
}
}
button {
disabled =
computer.getStatus() == MonTanaMiniComputer.ComputerStatus.WAITING && computer.rewindIndex >= 0
+"back"
onClickFunction = {
computer.back()
mainView.requestUpdate()
display.requestUpdate()
}
}
button {
+"step"
onClickFunction = {
computer.step()
mainView.requestUpdate()
display.requestUpdate()
}
}
button {
+"reset"
onClickFunction = {
computer.initMemory()
mainView.requestUpdate()
display.requestUpdate()
}
}
}
}
}
}

View File

@@ -0,0 +1,173 @@
package mtmc.view
import kotlinx.html.canvas
import kotlinx.html.div
import mtmc.display
import mtmc.emulator.BufferedImageData
import mtmc.emulator.BufferedImageDataWebGl
import mtmc.emulator.MonTanaMiniComputer
import nl.astraeus.komp.HtmlBuilder
import nl.astraeus.komp.Komponent
import nl.astraeus.komp.currentElement
import org.khronos.webgl.Float32Array
import org.khronos.webgl.WebGLBuffer
import org.khronos.webgl.WebGLProgram
import org.khronos.webgl.WebGLRenderingContext
import org.khronos.webgl.WebGLShader
import org.khronos.webgl.WebGLTexture
import org.w3c.dom.HTMLCanvasElement
// language=GLSL
val vertexShader = """
attribute vec2 a_pos;
attribute vec2 a_uv;
varying vec2 v_uv;
void main() {
v_uv = a_uv;
gl_Position = vec4(a_pos, 0.0, 1.0);
}
"""
// language=GLSL
val fragmentShader = """
precision mediump float;
varying vec2 v_uv;
uniform sampler2D u_tex;
void main() {
gl_FragColor = texture2D(u_tex, v_uv);
}
""".trimIndent()
typealias GL = WebGLRenderingContext
class DiplayControlView(
val computer: MonTanaMiniComputer
) : Komponent() {
override fun HtmlBuilder.render() {
div("display") {
include(display)
}
}
}
class DisplayView(
val computer: MonTanaMiniComputer
) : Komponent() {
var ctx: WebGLRenderingContext? = null
var program: WebGLProgram? = null
var texture: WebGLTexture? = null
var buffer: WebGLBuffer? = null
override fun HtmlBuilder.render() {
canvas("display-canvas") {
width = "160px"
height = "144px"
val cv = currentElement() as? HTMLCanvasElement
ctx = cv?.getContext("webgl")?.unsafeCast<WebGLRenderingContext>()
if (program == null) {
createProgram()
createBuffer()
createTexture()
}
}
}
private fun createTexture() {
ctx?.let { gl: WebGLRenderingContext ->
texture = gl.createTexture()
gl.bindTexture(GL.TEXTURE_2D, texture)
// Set texture parameters for pixel-perfect rendering
gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE)
gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE)
gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.NEAREST)
gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.NEAREST)
}
}
private fun createBuffer() {
ctx?.let { gl ->
// Create quad vertices
val vertices = Float32Array(
arrayOf(
-1f, -1f, 0f, 1f, // position, texCoord
1f, -1f, 1f, 1f,
-1f, 1f, 0f, 0f,
1f, 1f, 1f, 0f
)
)
buffer = gl.createBuffer();
gl.bindBuffer(GL.ARRAY_BUFFER, buffer);
gl.bufferData(GL.ARRAY_BUFFER, vertices, GL.STATIC_DRAW);
}
}
private fun createProgram() {
val vs = createShader(WebGLRenderingContext.VERTEX_SHADER, vertexShader)
val fs = createShader(WebGLRenderingContext.FRAGMENT_SHADER, fragmentShader)
val prog = ctx?.createProgram()
if (vs != null && fs != null && prog != null) {
ctx?.attachShader(prog, vs)
ctx?.attachShader(prog, fs)
ctx?.linkProgram(prog)
}
program = prog
}
private fun createShader(type: Int, source: String): WebGLShader? {
var result: WebGLShader? = null
ctx?.let { gl ->
result = gl.createShader(type)
result?.let { shader ->
gl.shaderSource(shader, source)
gl.compileShader(shader)
}
}
return result
}
override fun renderUpdate() {
// move data to canvas
val buffer = computer.display.buffer
if (buffer is BufferedImageData) {
//ctx?.putImageData(buffer.imageData, 0.0, 0.0)
} else if (buffer is BufferedImageDataWebGl) {
ctx?.let { gl ->
gl.clear(GL.COLOR_BUFFER_BIT)
gl.useProgram(program)
val positionLocation = gl.getAttribLocation(program, "a_pos")
val texCoordLocation = gl.getAttribLocation(program, "a_uv")
gl.bindBuffer(GL.ARRAY_BUFFER, this.buffer)
gl.enableVertexAttribArray(positionLocation)
gl.vertexAttribPointer(positionLocation, 2, GL.FLOAT, false, 16, 0)
gl.enableVertexAttribArray(texCoordLocation)
gl.vertexAttribPointer(texCoordLocation, 2, GL.FLOAT, false, 16, 8)
gl.bindTexture(GL.TEXTURE_2D, texture);
gl.texImage2D(
GL.TEXTURE_2D,
0, // level
GL.RGBA, // internal format
buffer.width,
buffer.height,
0, // border
GL.RGBA, // format
GL.UNSIGNED_BYTE, // type
buffer.data // data
)
// Draw quad
gl.drawArrays(GL.TRIANGLE_STRIP, 0, 4);
}
}
}
}

View File

@@ -0,0 +1,34 @@
package mtmc.view
import kotlinx.html.div
import mtmc.emulator.MonTanaMiniComputer
import nl.astraeus.komp.HtmlBuilder
import nl.astraeus.komp.Komponent
class MTMCView(
val computer: MonTanaMiniComputer
) : Komponent() {
val controlView = ControlView(computer)
val registerView = RegisterView(computer)
val memoryView = MemoryView(computer)
val displayView = DiplayControlView(computer)
val consoleView = ConsoleView(computer)
override fun HtmlBuilder.render() {
div("container") {
div("left-column") {
include(controlView)
include(registerView)
include(memoryView)
}
div("middle-column") {
include(displayView)
include(consoleView)
}
div("right-column") {
+"Files"
}
}
}
}

View File

@@ -0,0 +1,147 @@
package mtmc.view
import kotlinx.html.classes
import kotlinx.html.div
import kotlinx.html.table
import kotlinx.html.td
import kotlinx.html.title
import kotlinx.html.tr
import mtmc.asm.instructions.Instruction
import mtmc.emulator.MonTanaMiniComputer
import mtmc.emulator.Register
import nl.astraeus.komp.HtmlBuilder
import nl.astraeus.komp.Komponent
fun ByteArray.asHex(address: Int): String {
val value = getShort(address)
return value.toHexString()
}
fun ByteArray.getShort(address: Int): Short {
val value = (this[address].toInt() and 0xff) * 256 + (this[address + 1].toInt() and 0xff)
return value.toShort()
}
enum class DisplayFormat {
DYN,
HEX,
DEC,
INS,
STR
}
private val ASCII_CODES = arrayOf<String>(
"NUL", "SOH", "STX", "ETX",
"EOT", "ENT", "ACK", "BEL",
"BS", "\\t", "\\n", "VT",
"\\f", "\\r", "SO", "SI",
"DLE", "DC1", "DC2", "DC3",
"DC4", "NAK", "SYN", "ETB",
"CAN", "EM", "SUB", "ESC",
"FS", "GS", "RS", "US",
)
class MemoryView(
val computer: MonTanaMiniComputer
) : Komponent() {
var displayFormat: DisplayFormat = DisplayFormat.DYN
override fun HtmlBuilder.render() {
div("memory-panel") {
div("memory-header") {
+"Memory"
}
div("memory") {
table {
for (baseAddress in 0..<computer.memory.size step 16) {
tr {
var previousInstruction = 0x0fff.toShort()
for (offset in 0..<8) {
val address = baseAddress + offset * 2
val memoryClass = classFor(address)
val df = computeMemoryFormat(memoryClass)
val value = computer.memory.getShort(address)
td {
when (memoryClass) {
"curr" -> classes += "current-address"
"code" -> classes += "code"
"data" -> classes += "data"
"heap" -> classes += "heap"
"sta" -> classes += "stack"
}
title = address.toString() + " - " + value.toHexString()
when (df) {
DisplayFormat.DEC -> +value.toInt().toString()
DisplayFormat.INS -> {
+Instruction.disassemble(
value,
previousInstruction
)
previousInstruction = value
}
DisplayFormat.HEX -> +value.toHexString()
DisplayFormat.STR -> +get1252String(value)
else -> +"?"
}
}
}
}
}
}
}
}
}
fun classFor(address: Int): String = if (address >= computer.getRegisterValue(Register.SP)) {
"sta"
} else if (address == computer.getRegisterValue(Register.PC).toInt()) {
"curr"
} else if (address <= computer.getRegisterValue(Register.CB)) {
"code"
} else if (address <= computer.getRegisterValue(Register.DB)) {
"data"
} else if (address <= computer.getRegisterValue(Register.BP)) {
"heap"
} else {
""
}
private fun computeMemoryFormat(memoryClass: String?): DisplayFormat {
return if (displayFormat == DisplayFormat.DYN) {
when (memoryClass) {
"sta" -> DisplayFormat.DEC
"code", "curr" -> DisplayFormat.INS
"data", "heap" -> DisplayFormat.STR
else -> DisplayFormat.HEX
}
} else {
displayFormat
}
}
fun get1252String(value: Short): String {
val topByte = (value.toInt() ushr 8).toByte()
val bottomByte = value.toByte()
return if (topByte.toInt() != 0) {
get1252String(topByte) + " " + get1252String(
bottomByte
)
} else {
get1252String(bottomByte)
}
}
private fun get1252String(value: Byte): String = if (value in 0..<32) {
ASCII_CODES[value.toInt()]
} else {
"" + (value.toInt() and 0xff).toChar()
}
}

View File

@@ -0,0 +1,117 @@
package mtmc.view
import kotlinx.browser.document
import kotlinx.dom.addClass
import kotlinx.dom.removeClass
import kotlinx.html.TABLE
import kotlinx.html.classes
import kotlinx.html.div
import kotlinx.html.hr
import kotlinx.html.id
import kotlinx.html.span
import kotlinx.html.table
import kotlinx.html.td
import kotlinx.html.tr
import mtmc.emulator.MonTanaMiniComputer
import mtmc.emulator.Register
import nl.astraeus.komp.HtmlBuilder
import nl.astraeus.komp.Komponent
import org.w3c.dom.HTMLElement
class RegisterView(
val computer: MonTanaMiniComputer
) : Komponent() {
override fun HtmlBuilder.render() {
div {
table("register-table") {
for (index in 0..<16) {
showRegister(index)
}
tr {
td {
colSpan = "3"
hr {}
}
}
showRegister(16)
showRegister(17)
tr {
td("flags") {
colSpan = "3"
span {
+"flags t:"
div("blinken") {
id = "flags-t"
if (!computer.isFlagTestBitSet) {
classes += "off"
}
}
}
span {
+"o:"
div("blinken") {
id = "flags-o"
classes += "off"
}
}
span {
+"e:"
div("blinken") {
id = "flags-e"
if (computer.getStatus() != MonTanaMiniComputer.ComputerStatus.PERMANENT_ERROR) {
classes += "off"
}
}
}
}
}
}
}
}
override fun renderUpdate() {
for (register in 0..<18) {
val value = computer.registerFile[register]
for (bit in 15 downTo 0) {
val element = document.getElementById("bit-$bit-$register") as? HTMLElement ?: continue
if (value.toInt() and (1 shl bit) == 0) {
element.addClass("off")
} else {
element.removeClass("off")
}
}
val element = document.getElementById("register-$register") as? HTMLElement ?: continue
element.innerText = "${value.toInt() and 0xffff}"
}
}
private fun TABLE.showRegister(register: Int) {
val name = Register.fromInteger(register)
val value = computer.registerFile[register]
tr {
td {
+name
}
td("register-lights") {
for (bit in 15 downTo 0) {
div("blinken") {
id = "bit-$bit-$register"
if (value.toInt() and (1 shl bit) == 0) {
classes += "off"
}
if (bit % 4 == 0) {
classes += "space"
}
}
}
}
td("align-right") {
id = "register-$register"
+"${value.toInt() and 0xffff}"
}
}
}
}

View File

@@ -0,0 +1,256 @@
:root {
--pdp-blue: #243571;
--pdp-light-blue: #3286ce;
--pdp-beige: #fdfddc;
--pdp-white: #f1f1f6;
--pdp-off-white: #F0EBCD;
--filetree-gray: #666;
}
html, body {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
overscroll-behavior: none;
}
.container {
display: grid;
grid-template-columns: 450px 450px 1fr;
grid-template-rows: 1fr;
overflow-y: auto;
gap: 5px;
background-color: var(--pdp-white);
}
.left-column {
grid-template-rows: auto auto minmax(0, 1fr);
display: grid;
grid-gap: 4px;
height: 100dvh;
max-height: 100dvh;
min-width: 450px;
max-width: 450px;
}
.middle-column {
background-color: #eee;
grid-template-rows: auto minmax(0, 1fr);
display: grid;
grid-gap: 4px;
height: 100dvh;
max-height: 100dvh;
min-width: 450px;
max-width: 450px;
}
.right-column {
background-color: #eee;
grid-template-rows: auto;
display: grid;
grid-gap: 4px;
height: 100dvh;
max-height: 100dvh;
min-width: 450px;
}
.align-right {
text-align: right;
}
/* control */
.control-panel > * {
margin: 4px;
}
.control-header {
background-color: var(--pdp-blue);
color: var(--pdp-white);
font-family: monospace;
font-size: 32px;
padding: 4px 24px;
margin-left: 0;
margin-right: 0;
}
.control-header span {
display: inline-block;
margin-top: -6px;
margin-bottom: -6px;
padding: 4px 8px;
font-weight: bold;
font-size: 38px;
border-right: 4px solid white;
border-left: 4px solid white;
}
.control-header span:not(:first-child) {
border-left: none;
}
.control-secondary {
background-color: var(--pdp-light-blue);
color: var(--pdp-white);
font-family: monospace;
font-size: 18px;
font-style: italic;
text-align: center;
padding: 4px 4px;
margin-left: 0;
margin-right: 0;
}
.control-buttons {
text-align: right;
}
/* registers */
table.register-table {
font-family: monospace;
width: 100%;
padding: 5px;
background-color: black;
color: white;
border-radius: 8px;
}
table.register-table tr td.align-right {
text-align: right;
min-width: 100px;
}
table.register-table tr td.register-lights {
padding-left: 20px;
}
table.register-table tr td.flags {
text-align: center;
}
table.register-table tr td.flags span {
margin-left: 8px;
}
table.register-table tr td.flags span .blinken {
margin-left: 4px;
top: 2px;
position: relative;
}
.blinken {
display: inline-block;
margin-right: 2px;
height: 6px;
width: 6px;
border-radius: 6px;
padding: 3px;
background-color: red;
}
.blinken.off {
background-color: #5c0119;
}
.blinken.space {
margin-right: 8px;
}
/* memory */
.memory-panel {
background-color: #eee;
display: flex;
flex-direction: column;
}
.memory-header {
font-family: monospace;
font-weight: bold;
font-size: 16px;
/* overflow: auto; */
padding: 4px;
}
.memory {
overflow: auto;
}
.memory table tr td {
font-family: monospace;
font-size: 8px;
}
.current-address {
background-color: #78e878;
}
.code {
background-color: darkseagreen;
}
.data {
background-color: lightgoldenrodyellow;
}
.heap {
background-color: lightcoral;
}
.stack {
background-color: lightsalmon;
}
/* display */
.display {
min-height: 480px;
}
.display-canvas {
padding: 24px 64px;
width: 320px;
height: 288px;
image-rendering: pixelated; /* Keeps sharp pixels, no smoothing */
image-rendering: -moz-crisp-edges; /* Firefox */
image-rendering: crisp-edges; /* Standard */
}
/* console */
.console {
color: #ffcc00;
background-color: #35291c;
white-space: pre-wrap;
font-family: monospace;
font-weight: bold;
padding: 5px;
overflow: auto;
}
.console-prompt {
margin-right: 6px;
}
input.console-input {
color: #ffcc00;
background-color: #35291c;
border: none;
outline: none;
font-weight: bold;
font-family: monospace;
}
.small-button {
transform: rotate(-25deg) translateY(20px) translateX(-20px);
margin: 2px;
width: 50px;
background-color: #7e777b;
border: 1px solid dimgray;
border-radius: 8px;
color: whitesmoke;
box-shadow: rgb(0, 0, 0, .2) 2px 2px 1px 1px;
}

Some files were not shown because too many files have changed in this diff Show More