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"> <component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" /> <file type="web" url="file://$PROJECT_DIR$" />
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="corretto-21" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="corretto-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" /> <output url="file://$PROJECT_DIR$/out" />
</component> </component>
<component name="accountSettings"> <component name="accountSettings">

7
.idea/runConfigurations/MTMC_debug.xml generated Normal file
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) @file:OptIn(ExperimentalDistributionDsl::class)
import org.codehaus.groovy.tools.shell.util.Logger.io import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalDistributionDsl import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalDistributionDsl
plugins { plugins {
kotlin("multiplatform") version "2.1.20" kotlin("multiplatform") version "2.1.20"
} }
group = "nl.astraeus" group = "nl.astraeus"
version = "0.1.0-SNAPSHOT" version = "0.1.0-SNAPSHOT"
repositories { repositories {
mavenCentral() mavenCentral()
maven { maven {
url = uri("https://gitea.astraeus.nl/api/packages/rnentjes/maven") url = uri("https://gitea.astraeus.nl/api/packages/rnentjes/maven")
} }
maven { maven {
url = uri("https://gitea.astraeus.nl:8443/api/packages/rnentjes/maven") url = uri("https://gitea.astraeus.nl:8443/api/packages/rnentjes/maven")
} }
} }
kotlin { kotlin {
jvmToolchain(17) jvmToolchain(21)
jvm() jvm {
js { @OptIn(ExperimentalKotlinGradlePluginApi::class)
binaries.executable() binaries {
browser { // Configures a JavaExec task named "runJvm" and a Gradle distribution for the "main" compilation in this target
distribution { executable {
outputDirectory.set(File("$projectDir/web/")) mainClass.set("mtmc.MainKt")
} }
}
} }
sourceSets { }
val commonMain by getting { js {
dependencies { binaries.executable()
api("nl.astraeus:kotlin-simple-logging:1.1.1") browser {
api("nl.astraeus:kotlin-css-generator:1.0.10") distribution {
outputDirectory.set(File("$projectDir/web/"))
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.0") }
}
}
val commonTest by getting
val jvmMain by getting {
dependencies {
implementation("io.undertow:undertow-core:2.3.14.Final")
implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:0.11.0")
implementation("org.xerial:sqlite-jdbc:3.32.3.2")
implementation("com.zaxxer:HikariCP:4.0.3")
implementation("nl.astraeus:simple-jdbc-stats:1.6.1") {
exclude(group = "org.slf4j", module = "slf4j-api")
}
implementation("io.pebbletemplates:pebble:3.2.3")
implementation("com.google.code.gson:gson:2.12.1")
}
}
val jvmTest by getting {
dependencies {
}
}
val jsMain by getting {
dependencies {
implementation("nl.astraeus:kotlin-komponent:1.2.5")
}
}
val jsTest by getting
} }
}
// Add this to enable ExperimentalStdlibApi globally
sourceSets.all {
languageSettings.optIn("kotlin.ExperimentalStdlibApi")
}
sourceSets {
val commonMain by getting {
dependencies {
api("nl.astraeus:kotlin-simple-logging:1.1.1")
}
}
val commonTest by getting
val jvmMain by getting {
dependencies {
implementation("io.undertow:undertow-core:2.3.14.Final")
implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:0.11.0")
implementation("org.xerial:sqlite-jdbc:3.32.3.2")
implementation("com.zaxxer:HikariCP:4.0.3")
implementation("nl.astraeus:simple-jdbc-stats:1.6.1") {
exclude(group = "org.slf4j", module = "slf4j-api")
}
implementation("io.pebbletemplates:pebble:3.2.3")
implementation("com.google.code.gson:gson:2.12.1")
}
}
val jvmTest by getting {
dependencies {
}
}
val jsMain by getting {
dependencies {
implementation("nl.astraeus:kotlin-komponent:1.2.8")
}
}
val jsTest by getting
}
} }

View File

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

View File

@@ -4,9 +4,8 @@ import mtmc.tokenizer.MTMCToken
abstract class ASMElement( abstract class ASMElement(
val labels: List<MTMCToken>, val labels: List<MTMCToken>,
@JvmField var lineNumber: Int var lineNumber: Int
) : HasLocation { ) : HasLocation {
@JvmField
var errors: MutableList<ASMError> = ArrayList() var errors: MutableList<ASMError> = ArrayList()
override var location: Int = 0 override var location: Int = 0
override var sizeInBytes: Int = 0 override var sizeInBytes: Int = 0

View File

@@ -2,10 +2,9 @@ package mtmc.asm
import mtmc.tokenizer.MTMCToken import mtmc.tokenizer.MTMCToken
@JvmRecord
data class ASMError( data class ASMError(
@JvmField val token: MTMCToken, val token: MTMCToken,
@JvmField val error: String val error: String
) { ) {
fun formattedErrorMessage(): String { fun formattedErrorMessage(): String {
return "Line " + token.line + ": " + error return "Line " + token.line + ": " + error

View File

@@ -25,12 +25,16 @@ import mtmc.emulator.Register.Companion.isReadable
import mtmc.emulator.Register.Companion.isWriteable import mtmc.emulator.Register.Companion.isWriteable
import mtmc.os.SysCall import mtmc.os.SysCall
import mtmc.os.exec.Executable import mtmc.os.exec.Executable
import mtmc.os.fs.File
import mtmc.tokenizer.MTMCToken import mtmc.tokenizer.MTMCToken
import mtmc.tokenizer.MTMCTokenizer import mtmc.tokenizer.MTMCTokenizer
import java.io.File
import java.nio.charset.StandardCharsets fun MutableList<MTMCToken>.poll() = this.removeFirst()
import java.util.* fun MutableList<MTMCToken>.peekFirst() = if (this.isEmpty()) {
import java.util.stream.Collectors null
} else {
this[0]
}
class Assembler { class Assembler {
var instructions: MutableList<Instruction> = ArrayList<Instruction>() var instructions: MutableList<Instruction> = ArrayList<Instruction>()
@@ -88,7 +92,7 @@ class Assembler {
var location = 0 var location = 0
var originalLineNumber = 0 var originalLineNumber = 0
val currentLocals: MutableMap<String?, LocalInfo?> = TreeMap<String?, LocalInfo?>() val currentLocals: MutableMap<String?, LocalInfo?> = mutableMapOf()
for (instruction in instructions) { for (instruction in instructions) {
val asmLineNumber = instruction.lineNumber val asmLineNumber = instruction.lineNumber
if (instruction is MetaInstruction) { if (instruction is MetaInstruction) {
@@ -145,10 +149,8 @@ class Assembler {
val result = assemble(srcName, asm) val result = assemble(srcName, asm)
if (!result.errors.isEmpty()) { if (!result.errors.isEmpty()) {
throw RuntimeException( throw RuntimeException(
"Errors:\n" + result.errors "Errors:\n" + result.errors.joinToString("\n") { e: ASMError? -> " - " + e!!.formattedErrorMessage() }
.stream() )
.map<String> { e: ASMError? -> " - " + e!!.formattedErrorMessage() }
.collect(Collectors.joining("\n")))
} }
return Executable( return Executable(
@@ -261,7 +263,10 @@ class Assembler {
for (labelToken in labelTokens) { for (labelToken in labelTokens) {
val labelData = Data(labelTokens, labelToken.line) val labelData = Data(labelTokens, labelToken.line)
if (hasLabel(labelToken.stringValue)) { if (hasLabel(labelToken.stringValue)) {
labelData.addError(tokens.poll(), "Label already defined: " + labelToken.stringValue) labelData.addError(
tokens.removeFirst(),
"Label already defined: " + labelToken.stringValue
)
} else { } else {
this.labels.put(labelToken.labelValue(), labelData) this.labels.put(labelToken.labelValue(), labelData)
} }
@@ -274,7 +279,7 @@ class Assembler {
} }
} }
private fun parseMetaDirective(tokens: LinkedList<MTMCToken>): Boolean { private fun parseMetaDirective(tokens: MutableList<MTMCToken>): Boolean {
if (tokens.size >= 2 && tokens.get(0).type == MTMCToken.TokenType.AT) { if (tokens.size >= 2 && tokens.get(0).type == MTMCToken.TokenType.AT) {
tokens.removeFirst() tokens.removeFirst()
val metaInstruction = MetaInstruction(tokens.removeFirst()) val metaInstruction = MetaInstruction(tokens.removeFirst())
@@ -306,26 +311,26 @@ class Assembler {
return false return false
} }
private fun parseModeFlag(tokens: LinkedList<MTMCToken>): ASMMode? { private fun parseModeFlag(tokens: MutableList<MTMCToken>): ASMMode? {
if (tokens.size >= 2 && tokens.get(0).type == MTMCToken.TokenType.DOT && tokens.get(1).type == MTMCToken.TokenType.IDENTIFIER && tokens.get( if (tokens.size >= 2 && tokens.get(0).type == MTMCToken.TokenType.DOT && tokens.get(1).type == MTMCToken.TokenType.IDENTIFIER && tokens.get(
0 0
).end == tokens.get(1).start && (tokens.get(1).stringValue == "data" || ).end == tokens.get(1).start && (tokens.get(1).stringValue == "data" ||
tokens.get(1).stringValue == "text") tokens.get(1).stringValue == "text")
) { ) {
return ASMMode.valueOf(tokens.get(1).stringValue.uppercase(Locale.getDefault())) return ASMMode.valueOf(tokens.get(1).stringValue.uppercase())
} }
return null return null
} }
private fun parseData(tokens: LinkedList<MTMCToken>, labelTokens: MutableList<MTMCToken>) { private fun parseData(tokens: MutableList<MTMCToken>, labelTokens: MutableList<MTMCToken>) {
lastLabels = mutableListOf() lastLabels = mutableListOf()
var dataToken = tokens.poll() var dataToken = tokens.removeFirst()
var dataElt = Data(labelTokens, if (dataToken == null) 0 else dataToken.line) var dataElt = Data(labelTokens, if (dataToken == null) 0 else dataToken.line)
if (dataToken != null) { if (dataToken != null) {
if (dataToken.type == MTMCToken.TokenType.STRING) { if (dataToken.type == MTMCToken.TokenType.STRING) {
val stringBytes = dataToken.stringValue.toByteArray(StandardCharsets.US_ASCII) val stringBytes = dataToken.stringValue.encodeToByteArray()
val nullTerminated = ByteArray(stringBytes.size + 1) val nullTerminated = ByteArray(stringBytes.size + 1)
System.arraycopy(stringBytes, 0, nullTerminated, 0, stringBytes.size) stringBytes.copyInto(nullTerminated, 0, 0, stringBytes.size)
nullTerminated[stringBytes.size] = '\u0000'.code.toByte() nullTerminated[stringBytes.size] = '\u0000'.code.toByte()
dataElt.setValue(dataToken, nullTerminated) dataElt.setValue(dataToken, nullTerminated)
} else if (isInteger(dataToken)) { } else if (isInteger(dataToken)) {
@@ -338,7 +343,7 @@ class Assembler {
byteArrayOf((integerValue ushr 8).toByte(), integerValue.toByte()) byteArrayOf((integerValue ushr 8).toByte(), integerValue.toByte())
) )
} else if (dataToken.type == MTMCToken.TokenType.DOT) { } else if (dataToken.type == MTMCToken.TokenType.DOT) {
dataToken = tokens.poll() dataToken = tokens.removeFirst()
dataElt = Data(labelTokens, dataToken.line) dataElt = Data(labelTokens, dataToken.line)
if (dataToken.stringValue == "int") { if (dataToken.stringValue == "int") {
val intToken = requireIntegerToken(tokens, dataElt, MonTanaMiniComputer.MEMORY_SIZE) val intToken = requireIntegerToken(tokens, dataElt, MonTanaMiniComputer.MEMORY_SIZE)
@@ -359,7 +364,7 @@ class Assembler {
dataElt.addError(dataToken, "only data types are .int, .byte, and .image") dataElt.addError(dataToken, "only data types are .int, .byte, and .image")
} }
} else if (dataToken.type == MTMCToken.TokenType.MINUS) { } else if (dataToken.type == MTMCToken.TokenType.MINUS) {
val nextToken = tokens.poll() // get next val nextToken = tokens.removeFirst() // get next
if (nextToken == null || (nextToken.type != MTMCToken.TokenType.INTEGER && nextToken.type != MTMCToken.TokenType.HEX && nextToken.type != MTMCToken.TokenType.BINARY)) { if (nextToken == null || (nextToken.type != MTMCToken.TokenType.INTEGER && nextToken.type != MTMCToken.TokenType.HEX && nextToken.type != MTMCToken.TokenType.BINARY)) {
dataElt.addError(dataToken, "Number is too negative") dataElt.addError(dataToken, "Number is too negative")
} else { } else {
@@ -380,7 +385,7 @@ class Assembler {
for (labelToken in labelTokens) { for (labelToken in labelTokens) {
if (hasLabel(labelToken.stringValue)) { if (hasLabel(labelToken.stringValue)) {
dataElt.addError(tokens.poll(), "Label already defined: " + labelToken.stringValue) dataElt.addError(tokens.removeFirst(), "Label already defined: " + labelToken.stringValue)
} else { } else {
labels.put(labelToken.labelValue(), dataElt) labels.put(labelToken.labelValue(), dataElt)
} }
@@ -395,7 +400,7 @@ class Assembler {
): Graphic { ): Graphic {
val graphic = Graphic(labelTokens, token.line) val graphic = Graphic(labelTokens, token.line)
val filename = token.stringValue val filename = token.stringValue
val file = File(File(this.srcName).getParent(), filename) val file = File(File(this.srcName).parent, filename)
val index = graphics.size val index = graphics.size
data.setValue(token, byteArrayOf(((index shr 8) and 0xFF).toByte(), (index and 0xFF).toByte())) data.setValue(token, byteArrayOf(((index shr 8) and 0xFF).toByte(), (index and 0xFF).toByte()))
@@ -405,9 +410,16 @@ class Assembler {
return graphic return graphic
} }
private fun parseInstruction(tokens: LinkedList<MTMCToken>, labelTokens: MutableList<MTMCToken>) { private fun parseInstruction(
tokens: MutableList<MTMCToken>,
labelTokens: MutableList<MTMCToken>
) {
var tokens = tokens var tokens = tokens
var instructionToken = tokens.peekFirst() var instructionToken = if (tokens.isEmpty()) {
null
} else {
tokens[0]
}
if (instructionToken == null) return if (instructionToken == null) return
lastLabels = mutableListOf() lastLabels = mutableListOf()
@@ -510,7 +522,7 @@ class Assembler {
} }
// if there is a stack register specified, consume it // if there is a stack register specified, consume it
if (!tokens.isEmpty() && tokens.peekFirst().type == MTMCToken.TokenType.IDENTIFIER) { if (!tokens.isEmpty() && tokens.peekFirst()?.type == MTMCToken.TokenType.IDENTIFIER) {
val stackReg = requireReadableRegister(tokens, stackInst) val stackReg = requireReadableRegister(tokens, stackInst)
stackInst.setStackRegister(stackReg) stackInst.setStackRegister(stackReg)
} }
@@ -550,7 +562,7 @@ class Assembler {
loadInst.setPointerToken(pointerReg) loadInst.setPointerToken(pointerReg)
// if there is an offset register specified, consume it // if there is an offset register specified, consume it
if (!tokens.isEmpty() && tokens.peekFirst().type == MTMCToken.TokenType.IDENTIFIER) { if (!tokens.isEmpty() && tokens.peekFirst()?.type == MTMCToken.TokenType.IDENTIFIER) {
val offsetReg = requireReadableRegister(tokens, loadInst) val offsetReg = requireReadableRegister(tokens, loadInst)
loadInst.setOffsetToken(offsetReg) loadInst.setOffsetToken(offsetReg)
} }
@@ -598,7 +610,7 @@ class Assembler {
//=================================================== //===================================================
// tokenization helper functions // tokenization helper functions
//=================================================== //===================================================
private fun requireSysCall(tokens: LinkedList<MTMCToken>, inst: Instruction): MTMCToken { private fun requireSysCall(tokens: MutableList<MTMCToken>, inst: Instruction): MTMCToken {
val sysCallType = tokens.poll() val sysCallType = tokens.poll()
if (sysCallType == null) { if (sysCallType == null) {
inst.addError("Syscall required") inst.addError("Syscall required")
@@ -610,7 +622,7 @@ class Assembler {
return sysCallType!! return sysCallType!!
} }
private fun requireAluOp(tokens: LinkedList<MTMCToken>, inst: Instruction): MTMCToken { private fun requireAluOp(tokens: MutableList<MTMCToken>, inst: Instruction): MTMCToken {
val sysCallType = tokens.poll() val sysCallType = tokens.poll()
if (sysCallType == null) { if (sysCallType == null) {
inst.addError("Syscall required") inst.addError("Syscall required")
@@ -623,7 +635,7 @@ class Assembler {
} }
private fun requireWriteableRegister( private fun requireWriteableRegister(
tokens: LinkedList<MTMCToken>, tokens: MutableList<MTMCToken>,
instruction: Instruction instruction: Instruction
): MTMCToken { ): MTMCToken {
val nextToken = tokens.poll() val nextToken = tokens.poll()
@@ -638,7 +650,7 @@ class Assembler {
} }
private fun requireReadableRegister( private fun requireReadableRegister(
tokens: LinkedList<MTMCToken>, tokens: MutableList<MTMCToken>,
instruction: Instruction instruction: Instruction
): MTMCToken { ): MTMCToken {
val nextToken = tokens.poll() val nextToken = tokens.poll()
@@ -652,7 +664,7 @@ class Assembler {
return nextToken!! return nextToken!!
} }
private fun requireALUOp(tokens: LinkedList<MTMCToken>, instruction: Instruction): MTMCToken { private fun requireALUOp(tokens: MutableList<MTMCToken>, instruction: Instruction): MTMCToken {
val nextToken = tokens.poll() val nextToken = tokens.poll()
if (nextToken == null || nextToken.type != MTMCToken.TokenType.IDENTIFIER || !isALUOp(nextToken.stringValue)) { if (nextToken == null || nextToken.type != MTMCToken.TokenType.IDENTIFIER || !isALUOp(nextToken.stringValue)) {
instruction.addError("ALU operation required") instruction.addError("ALU operation required")
@@ -660,7 +672,7 @@ class Assembler {
return nextToken!! return nextToken!!
} }
private fun requireString(tokens: LinkedList<MTMCToken>, instruction: ASMElement): MTMCToken { private fun requireString(tokens: MutableList<MTMCToken>, instruction: ASMElement): MTMCToken {
val nextToken = tokens.poll() val nextToken = tokens.poll()
if (nextToken == null || nextToken.type != MTMCToken.TokenType.STRING) { if (nextToken == null || nextToken.type != MTMCToken.TokenType.STRING) {
instruction.addError("String required") instruction.addError("String required")
@@ -669,7 +681,7 @@ class Assembler {
} }
private fun requireIntegerToken( private fun requireIntegerToken(
tokens: LinkedList<MTMCToken>, tokens: MutableList<MTMCToken>,
inst: ASMElement, inst: ASMElement,
max: Int max: Int
): MTMCToken { ): MTMCToken {
@@ -688,7 +700,7 @@ class Assembler {
} }
private fun requireToken( private fun requireToken(
tokens: LinkedList<MTMCToken>, tokens: MutableList<MTMCToken>,
type: MTMCToken.TokenType, type: MTMCToken.TokenType,
inst: ASMElement inst: ASMElement
): MTMCToken? { ): MTMCToken? {
@@ -700,7 +712,7 @@ class Assembler {
} }
private fun requireIntegerOrLabelReferenceToken( private fun requireIntegerOrLabelReferenceToken(
tokens: LinkedList<MTMCToken>, tokens: MutableList<MTMCToken>,
inst: LoadStoreInstruction inst: LoadStoreInstruction
): MTMCToken { ): MTMCToken {
val token = tokens.poll() val token = tokens.poll()
@@ -713,14 +725,14 @@ class Assembler {
} }
private val tokensForLine: LinkedList<MTMCToken> private val tokensForLine: MutableList<MTMCToken>
get() { get() {
val tokens = LinkedList<MTMCToken>() val tokens = mutableListOf<MTMCToken>()
if (tokenizer!!.more()) { if (tokenizer!!.more()) {
val first = tokenizer!!.consume() val first = tokenizer!!.consume()
tokens.add(first) tokens.add(first)
while (tokenizer!!.more() && while (tokenizer!!.more() &&
first.line == tokenizer!!.currentToken().line first.line == tokenizer!!.getCurrentToken().line
) { ) {
tokens.add(tokenizer!!.consume()) tokens.add(tokenizer!!.consume())
} }
@@ -742,51 +754,51 @@ class Assembler {
} }
companion object { companion object {
fun transformSyntheticInstructions(tokens: LinkedList<MTMCToken>): LinkedList<MTMCToken> { fun transformSyntheticInstructions(tokens: MutableList<MTMCToken>): MutableList<MTMCToken> {
if (!tokens.isEmpty()) { if (!tokens.isEmpty()) {
val first = tokens.peekFirst() val first = tokens.first()
if (first.type == MTMCToken.TokenType.IDENTIFIER) { if (first.type == MTMCToken.TokenType.IDENTIFIER) {
val stringVal = first.stringValue val stringVal = first.stringValue
if (stringVal.endsWith("i")) { if (stringVal.endsWith("i")) {
val op = stringVal.substring(0, stringVal.length - 1) val op = stringVal.substring(0, stringVal.length - 1)
if (isALUOp(op)) { if (isALUOp(op)) {
val syntheticImmediate = tokens.removeFirst() val syntheticImmediate = tokens.removeFirst()
tokens.addFirst(syntheticImmediate.cloneWithVal(op)) tokens.add(0, syntheticImmediate.cloneWithVal(op))
tokens.addFirst(syntheticImmediate.cloneWithVal("imm")) tokens.add(0, syntheticImmediate.cloneWithVal("imm"))
} }
} else if (stringVal.startsWith("s")) { } else if (stringVal.startsWith("s")) {
val op = stringVal.substring(1, stringVal.length) val op = stringVal.substring(1, stringVal.length)
if (isALUOp(op)) { if (isALUOp(op)) {
val syntheticImmediate = tokens.removeFirst() val syntheticImmediate = tokens.removeFirst()
tokens.addFirst(syntheticImmediate.cloneWithVal(op)) tokens.add(0, syntheticImmediate.cloneWithVal(op))
tokens.addFirst(syntheticImmediate.cloneWithVal("sop")) tokens.add(0, syntheticImmediate.cloneWithVal("sop"))
} }
} else if (stringVal == "la") { } else if (stringVal == "la") {
val syntheticImmediate = tokens.removeFirst() val syntheticImmediate = tokens.removeFirst()
tokens.addFirst(syntheticImmediate.cloneWithVal("li")) tokens.add(0, syntheticImmediate.cloneWithVal("li"))
} else if (stringVal == "ret") { } else if (stringVal == "ret") {
val syntheticImmediate = tokens.removeFirst() val syntheticImmediate = tokens.removeFirst()
tokens.addFirst(syntheticImmediate.cloneWithVal("ra")) tokens.add(0, syntheticImmediate.cloneWithVal("ra"))
tokens.addFirst(syntheticImmediate.cloneWithVal("jr")) tokens.add(0, syntheticImmediate.cloneWithVal("jr"))
} }
} }
} }
return tokens return tokens
} }
private fun maybeGetLabels(tokens: LinkedList<MTMCToken>): MutableList<MTMCToken> { private fun maybeGetLabels(tokens: MutableList<MTMCToken>): MutableList<MTMCToken> {
val labels = LinkedList<MTMCToken>() val labels = mutableListOf<MTMCToken>()
while (!tokens.isEmpty() && tokens.getFirst().type == MTMCToken.TokenType.LABEL) { while (!tokens.isEmpty() && tokens.first().type == MTMCToken.TokenType.LABEL) {
val label = tokens.poll() val label = tokens.removeFirst()
labels.add(label!!) labels.add(label!!)
} }
return labels return labels
} }
private fun maybeGetLabelReference(tokens: LinkedList<MTMCToken>): MTMCToken? { private fun maybeGetLabelReference(tokens: MutableList<MTMCToken>): MTMCToken? {
var label: MTMCToken? = null var label: MTMCToken? = null
if (tokens.getFirst().type == MTMCToken.TokenType.IDENTIFIER) { if (tokens.first().type == MTMCToken.TokenType.IDENTIFIER) {
label = tokens.poll() label = tokens.removeFirst()
} }
return label return label
} }

View File

@@ -2,13 +2,12 @@ package mtmc.asm
import mtmc.emulator.DebugInfo import mtmc.emulator.DebugInfo
@JvmRecord
data class AssemblyResult( data class AssemblyResult(
@JvmField val code: ByteArray, val code: ByteArray,
val data: ByteArray, val data: ByteArray,
val graphics: Array<ByteArray>, val graphics: Array<ByteArray>,
@JvmField val debugInfo: DebugInfo?, val debugInfo: DebugInfo?,
@JvmField val errors: MutableList<ASMError> val errors: MutableList<ASMError>
) { ) {
fun printErrors(): String { fun printErrors(): String {
val builder = StringBuilder("Errors:\n") val builder = StringBuilder("Errors:\n")

View File

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

View File

@@ -0,0 +1,45 @@
package mtmc.asm.graphics
import mtmc.asm.ASMElement
import mtmc.tokenizer.MTMCToken
/**
*
* @author jbanes
*/
class Graphic(labels: MutableList<MTMCToken>, lineNumber: Int) : ASMElement(labels, lineNumber) {
private var filename: String? = null
var imageData: ByteArray = ByteArray(0)
fun setImage(filename: String) {
/* try {
var image = ImageIO.read(File(filename))
val buffer = ByteArrayOutputStream()
if (image.getWidth() > 1024 || image.getHeight() > 1024) {
addError(filename + " is too large. Maximum image size is 1024x1024")
}
image = convertImage(image)
ImageIO.write(image, "png", buffer)
this.filename = filename
this.imageData = buffer.toByteArray()
} catch (e: FileNotFoundException) {
e.printStackTrace()
addError("$filename not found")
} catch (e: IOException) {
e.printStackTrace()
addError(e.message ?: "error in Graphic") // TODO: Verify these messages are meaningful
}*/
}
override fun addError(err: String) {
addError(labels.last(), err)
}
override var sizeInBytes: Int
get() = 2
set(value) {}
}

View File

@@ -5,7 +5,6 @@ import mtmc.emulator.Register.Companion.fromInteger
import mtmc.emulator.Register.Companion.toInteger import mtmc.emulator.Register.Companion.toInteger
import mtmc.tokenizer.MTMCToken import mtmc.tokenizer.MTMCToken
import mtmc.util.BinaryUtils import mtmc.util.BinaryUtils
import java.util.Locale
class ALUInstruction( class ALUInstruction(
type: InstructionType, type: InstructionType,
@@ -38,7 +37,7 @@ class ALUInstruction(
} }
val isBinaryOp: Boolean val isBinaryOp: Boolean
get() = !ALUOp.valueOf(instructionToken.stringValue.uppercase(Locale.getDefault())).isUnary get() = !ALUOp.valueOf(instructionToken.stringValue.uppercase()).isUnary
override fun genCode(output: ByteArray, assembler: Assembler) { override fun genCode(output: ByteArray, assembler: Assembler) {
val opCode = ALUOp.toInteger(instructionToken.stringValue) val opCode = ALUOp.toInteger(instructionToken.stringValue)
@@ -62,13 +61,12 @@ class ALUInstruction(
} }
companion object { companion object {
@JvmStatic
fun disassemble(instruction: Short): String? { fun disassemble(instruction: Short): String? {
if (BinaryUtils.getBits(16, 4, instruction).toInt() == 1) { if (BinaryUtils.getBits(16, 4, instruction).toInt() == 1) {
val builder = StringBuilder() val builder = StringBuilder()
val opCode = BinaryUtils.getBits(12, 4, instruction) val opCode = BinaryUtils.getBits(12, 4, instruction)
val op = ALUOp.fromInt(opCode) val op = ALUOp.fromInt(opCode)
val aluOp = ALUOp.valueOf(op.uppercase(Locale.getDefault())) val aluOp = ALUOp.valueOf(op.uppercase())
if (aluOp == ALUOp.IMM) { if (aluOp == ALUOp.IMM) {
builder.append(op).append(" ") builder.append(op).append(" ")
builder.append(fromInteger(BinaryUtils.getBits(8, 4, instruction).toInt())).append(" ") builder.append(fromInteger(BinaryUtils.getBits(8, 4, instruction).toInt())).append(" ")

View File

@@ -1,7 +1,5 @@
package mtmc.asm.instructions package mtmc.asm.instructions
import java.util.*
enum class ALUOp(val opCode: Int, val isUnary: Boolean) { enum class ALUOp(val opCode: Int, val isUnary: Boolean) {
ADD(0x0000, false), ADD(0x0000, false),
SUB(0x0001, false), SUB(0x0001, false),
@@ -21,20 +19,17 @@ enum class ALUOp(val opCode: Int, val isUnary: Boolean) {
IMM(0x000F, true); IMM(0x000F, true);
companion object { companion object {
@JvmStatic
fun toInteger(instruction: String): Int { fun toInteger(instruction: String): Int {
return valueOf(instruction.uppercase(Locale.getDefault())).opCode return valueOf(instruction.uppercase()).opCode
} }
@JvmStatic
fun fromInt(opCode: Short): String { fun fromInt(opCode: Short): String {
return entries[opCode.toInt()].name.lowercase(Locale.getDefault()) return entries[opCode.toInt()].name.lowercase()
} }
@JvmStatic
fun isALUOp(op: String): Boolean { fun isALUOp(op: String): Boolean {
try { try {
val aluOp = valueOf(op.uppercase(Locale.getDefault())) val aluOp = valueOf(op.uppercase())
return true return true
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
return false return false

View File

@@ -7,9 +7,9 @@ import mtmc.emulator.MonTanaMiniComputer.Companion.isDoubleWordInstruction
import mtmc.tokenizer.MTMCToken import mtmc.tokenizer.MTMCToken
abstract class Instruction( abstract class Instruction(
@JvmField val type: InstructionType, val type: InstructionType,
labels: List<MTMCToken>, labels: List<MTMCToken>,
@JvmField val instructionToken: MTMCToken val instructionToken: MTMCToken
) : ASMElement(labels, instructionToken.line) { ) : ASMElement(labels, instructionToken.line) {
open fun validateLabel(assembler: Assembler) { open fun validateLabel(assembler: Assembler) {
// default does nothing // default does nothing
@@ -25,7 +25,6 @@ abstract class Instruction(
get() = type.sizeInBytes get() = type.sizeInBytes
companion object { companion object {
@JvmStatic
fun isInstruction(cmd: String): Boolean { fun isInstruction(cmd: String): Boolean {
return InstructionType.fromString(cmd) != null return InstructionType.fromString(cmd) != null
} }
@@ -58,7 +57,7 @@ abstract class Instruction(
if (ls != null) { if (ls != null) {
return ls return ls
} }
val jumpReg = JumpInstruction.disassemble(instruction) val jumpReg = JumpRegisterInstruction.disassemble(instruction)
if (jumpReg != null) { if (jumpReg != null) {
return jumpReg return jumpReg
} }
@@ -66,7 +65,7 @@ abstract class Instruction(
if (jump != null) { if (jump != null) {
return jump return jump
} }
return "<unknown>" return ""
} }
} }
} }

View File

@@ -1,9 +1,7 @@
package mtmc.asm.instructions package mtmc.asm.instructions
import java.util.* enum class InstructionType constructor(
val instructionClass: InstructionClass?,
enum class InstructionType @JvmOverloads constructor(
@JvmField val instructionClass: InstructionClass?,
val sizeInBytes: Int = 2 val sizeInBytes: Int = 2
) { ) {
SYS(InstructionClass.MISC), SYS(InstructionClass.MISC),
@@ -86,10 +84,9 @@ enum class InstructionType @JvmOverloads constructor(
} }
companion object { companion object {
@JvmStatic
fun fromString(string: String): InstructionType? { fun fromString(string: String): InstructionType? {
try { try {
return valueOf(string.uppercase(Locale.getDefault())) return valueOf(string.uppercase())
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
return null return null
} }

View File

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

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

View File

@@ -1,7 +1,5 @@
package mtmc.emulator package mtmc.emulator
import java.util.*
class MTMCIO { class MTMCIO {
var value: Int = 0 var value: Int = 0
@@ -17,12 +15,12 @@ class MTMCIO {
} }
fun keyPressed(key: String) { fun keyPressed(key: String) {
val button = Buttons.valueOf(key.uppercase(Locale.getDefault())) val button = Buttons.valueOf(key.uppercase())
value = value or button.mask value = value or button.mask
} }
fun keyReleased(key: String) { fun keyReleased(key: String) {
val button = Buttons.valueOf(key.uppercase(Locale.getDefault())) val button = Buttons.valueOf(key.uppercase())
value = value and button.mask.inv() value = value and button.mask.inv()
} }
} }

View File

@@ -4,19 +4,7 @@ import mtmc.asm.instructions.Instruction
import mtmc.os.MTOS import mtmc.os.MTOS
import mtmc.os.fs.FileSystem import mtmc.os.fs.FileSystem
import mtmc.util.BinaryUtils.getBits import mtmc.util.BinaryUtils.getBits
import java.lang.Byte import mtmc.util.Runnable
import java.util.*
import java.util.function.Consumer
import java.util.stream.IntStream
import kotlin.Array
import kotlin.Boolean
import kotlin.ByteArray
import kotlin.Int
import kotlin.IntArray
import kotlin.Long
import kotlin.Short
import kotlin.ShortArray
import kotlin.String
import kotlin.experimental.and import kotlin.experimental.and
import kotlin.experimental.inv import kotlin.experimental.inv
import kotlin.experimental.or import kotlin.experimental.or
@@ -30,7 +18,7 @@ class MonTanaMiniComputer {
var memory: ByteArray = ByteArray(MEMORY_SIZE) var memory: ByteArray = ByteArray(MEMORY_SIZE)
var breakpoints: ByteArray = ByteArray(MEMORY_SIZE) var breakpoints: ByteArray = ByteArray(MEMORY_SIZE)
private var status: ComputerStatus? = ComputerStatus.READY private var status: ComputerStatus? = ComputerStatus.READY
var speed: Int = 1000000 var speed: Int = 1
set(speed) { set(speed) {
field = speed field = speed
this.notifyOfExecutionUpdate() this.notifyOfExecutionUpdate()
@@ -43,10 +31,12 @@ class MonTanaMiniComputer {
var display: MTMCDisplay = MTMCDisplay(this) var display: MTMCDisplay = MTMCDisplay(this)
var clock: MTMCClock = MTMCClock(this) var clock: MTMCClock = MTMCClock(this)
var fileSystem: FileSystem = FileSystem(this) var fileSystem: FileSystem = FileSystem(this)
var rewindSteps: LinkedList<RewindStep>? = LinkedList<RewindStep>()
var rewindSteps = Array(MAX_REWIND_STEPS) { RewindStep() }
var rewindIndex = -1
// listeners // listeners
private val observers: MutableList<MTMCObserver> = ArrayList<MTMCObserver>() private val observers = mutableListOf<MTMCObserver>()
var debugInfo: DebugInfo? = null var debugInfo: DebugInfo? = null
private var currentRewindStep: RewindStep? = null private var currentRewindStep: RewindStep? = null
@@ -58,32 +48,33 @@ class MonTanaMiniComputer {
registerFile = ShortArray(Register.entries.size) registerFile = ShortArray(Register.entries.size)
memory = ByteArray(MEMORY_SIZE) memory = ByteArray(MEMORY_SIZE)
breakpoints = ByteArray(MEMORY_SIZE) breakpoints = ByteArray(MEMORY_SIZE)
rewindSteps = null
setRegisterValue( setRegisterValue(
Register.SP, Register.SP,
MEMORY_SIZE.toShort().toInt() MEMORY_SIZE.toShort().toInt()
) // default the stack pointer to the top of memory ) // default the stack pointer to the top of memory
rewindSteps = LinkedList<RewindStep>() rewindIndex = -1
observers!!.forEach(Consumer { obj: MTMCObserver? -> obj!!.computerReset() }) observers.forEach { obj ->
obj.computerReset()
}
} }
fun load(code: ByteArray, data: ByteArray, debugInfo: DebugInfo?) { fun load(
load(code, data, Array(0) { ByteArray(0) }, debugInfo) code: ByteArray,
} data: ByteArray,
graphics: Array<ByteArray> = arrayOf(),
fun load(code: ByteArray, data: ByteArray, graphics: Array<ByteArray>, debugInfo: DebugInfo?) { debugInfo: DebugInfo? = null
) {
this.debugInfo = debugInfo this.debugInfo = debugInfo
// reset memory // reset memory
initMemory() initMemory()
val codeBoundary = code.size val codeBoundary = code.size
System.arraycopy(code, 0, memory, 0, codeBoundary) code.copyInto(memory, 0, 0, codeBoundary)
setRegisterValue(Register.CB, codeBoundary - 1) setRegisterValue(Register.CB, codeBoundary - 1)
val dataBoundary = codeBoundary + data.size val dataBoundary = codeBoundary + data.size
System.arraycopy(data, 0, memory, codeBoundary, data.size) data.copyInto(memory, codeBoundary, 0, data.size)
setRegisterValue(Register.DB, dataBoundary - 1) setRegisterValue(Register.DB, dataBoundary - 1)
@@ -142,8 +133,9 @@ class MonTanaMiniComputer {
} }
fun fetchAndExecute() { fun fetchAndExecute() {
currentRewindStep = RewindStep() rewindIndex = (rewindIndex + 1) % rewindSteps.size
rewindSteps!!.push(currentRewindStep) rewindSteps[rewindIndex].index = 0
fetchCurrentInstruction() fetchCurrentInstruction()
val instruction = getRegisterValue(Register.IR) val instruction = getRegisterValue(Register.IR)
if (isDoubleWordInstruction(instruction)) { if (isDoubleWordInstruction(instruction)) {
@@ -158,7 +150,7 @@ class MonTanaMiniComputer {
} }
fun execInstruction(instruction: Short) { fun execInstruction(instruction: Short) {
observers!!.forEach(Consumer { o: MTMCObserver? -> o!!.beforeExecution(instruction) }) observers!!.forEach({ o: MTMCObserver? -> o!!.beforeExecution(instruction) })
val instructionType: Short = getBits(16, 4, instruction) val instructionType: Short = getBits(16, 4, instruction)
if (instructionType.toInt() == 0x0000) { // MISC if (instructionType.toInt() == 0x0000) { // MISC
val topNibble: Int = getBits(12, 4, instruction).toInt() val topNibble: Int = getBits(12, 4, instruction).toInt()
@@ -710,7 +702,9 @@ class MonTanaMiniComputer {
} else { } else {
badInstruction(instruction) badInstruction(instruction)
} }
observers.forEach(Consumer { o: MTMCObserver? -> o!!.afterExecution(instruction) }) observers.forEach { o: MTMCObserver? ->
o!!.afterExecution(instruction)
}
} }
val isFlagTestBitSet: Boolean val isFlagTestBitSet: Boolean
@@ -733,7 +727,11 @@ class MonTanaMiniComputer {
private fun badInstruction(instruction: Short) { private fun badInstruction(instruction: Short) {
setStatus(ComputerStatus.PERMANENT_ERROR) setStatus(ComputerStatus.PERMANENT_ERROR)
// TODO implement flags // TODO implement flags
console.println("BAD INSTRUCTION: 0x" + Integer.toHexString(instruction.toInt() and 0xFFFF)) console.println(
"BAD INSTRUCTION: 0x" + (instruction.toHexString()) + " at PC " + getRegisterValue(
Register.PC
)
)
} }
fun fetchCurrentInstruction() { fun fetchCurrentInstruction() {
@@ -746,14 +744,16 @@ class MonTanaMiniComputer {
} else { } else {
setRegisterValue(Register.DR, 0) setRegisterValue(Register.DR, 0)
} }
observers!!.forEach(Consumer { o: MTMCObserver? -> o!!.instructionFetched(instruction) }) observers.forEach { o: MTMCObserver? ->
o!!.instructionFetched(instruction)
}
} }
fun fetchWordFromMemory(address: Int): Short { fun fetchWordFromMemory(address: Int): Short {
val upperByte = fetchByteFromMemory(address).toShort() val upperByte = fetchByteFromMemory(address).toShort()
val lowerByte = fetchByteFromMemory(address + 1) val lowerByte = fetchByteFromMemory(address + 1)
var value = (upperByte.toInt() shl 8).toShort() var value = (upperByte.toInt() shl 8).toShort()
val i = value.toInt() or Byte.toUnsignedInt(lowerByte) val i = value.toInt() + (lowerByte.toInt() and 255)
value = i.toShort() value = i.toShort()
return value return value
} }
@@ -762,9 +762,8 @@ class MonTanaMiniComputer {
if (address < 0 || address >= memory.size) { if (address < 0 || address >= memory.size) {
setStatus(ComputerStatus.PERMANENT_ERROR) setStatus(ComputerStatus.PERMANENT_ERROR)
console.println( console.println(
"BAD MEMORY LOCATION ON READ: " + address + " (0x" + Integer.toHexString( "BAD MEMORY LOCATION ON READ: $address (0x" +
address and 0xFFFF (address and 0xFFFF).toHexString() + ")"
) + ")"
) )
return 0 return 0
} else { } else {
@@ -782,24 +781,22 @@ class MonTanaMiniComputer {
if (address < 0 || address >= memory.size) { if (address < 0 || address >= memory.size) {
setStatus(ComputerStatus.PERMANENT_ERROR) setStatus(ComputerStatus.PERMANENT_ERROR)
console.println( console.println(
"BAD MEMORY LOCATION ON WRITE: " + address + " (0x" + Integer.toHexString( "BAD MEMORY LOCATION ON WRITE: " + address + " (0x" +
address and 0xFFFF (address and 0xFFFF).toHexString() + ")"
) + ")"
) )
return return
} }
val currentValue = memory[address] val currentValue = memory[address]
addRewindStep(Runnable { memory[address] = currentValue }) addRewindStep { memory[address] = currentValue }
memory[address] = value memory[address] = value
observers!!.forEach(Consumer { o: MTMCObserver? -> o!!.memoryUpdated(address, value) }) observers.forEach { o: MTMCObserver? ->
o!!.memoryUpdated(address, value)
}
} }
private fun addRewindStep(runnable: Runnable?) { private fun addRewindStep(runnable: Runnable?) {
if (currentRewindStep != null && rewindSteps != null) { if (currentRewindStep != null) {
currentRewindStep!!.addSubStep(runnable) currentRewindStep!!.addSubStep(runnable)
if (rewindSteps!!.size > MAX_REWIND_STEPS) {
rewindSteps!!.removeLast()
}
} }
} }
@@ -812,11 +809,13 @@ class MonTanaMiniComputer {
// TODO mark as overflow // TODO mark as overflow
} }
val currentValue = registerFile[register] val currentValue = registerFile[register]
addRewindStep(Runnable { addRewindStep {
registerFile[register] = currentValue registerFile[register] = currentValue
}) }
registerFile[register] = value.toShort() registerFile[register] = value.toShort()
observers!!.forEach(Consumer { o: MTMCObserver? -> o!!.registerUpdated(register, value) }) observers.forEach { o: MTMCObserver? ->
o!!.registerUpdated(register, value)
}
} }
fun getRegisterValue(register: Register): Short { fun getRegisterValue(register: Register): Short {
@@ -845,12 +844,14 @@ class MonTanaMiniComputer {
breakpoints[address] = (if (active) 1.toByte() else 0.toByte()) breakpoints[address] = (if (active) 1.toByte() else 0.toByte())
} }
private fun start() { fun start() {
console.start() // start the interactive console console.start() // start the interactive console
} }
fun getBytesFromMemory(address: Int, length: Int): ByteArray { fun getBytesFromMemory(address: Int, length: Int): ByteArray {
return Arrays.copyOfRange(memory, address, address + length) val result = ByteArray(length)
memory.copyInto(result, 0, address, address + length)
return result
} }
val oS: MTOS val oS: MTOS
@@ -858,18 +859,15 @@ class MonTanaMiniComputer {
val memoryAddresses: Iterable<Int?> val memoryAddresses: Iterable<Int?>
get() = Iterable { get() = Iterable {
IntStream.range( (0..MEMORY_SIZE).iterator()
0,
MEMORY_SIZE
).iterator()
} }
fun addObserver(observer: MTMCObserver?) { fun addObserver(observer: MTMCObserver?) {
observers!!.add(observer!!) observers.add(observer!!)
} }
fun removeObserver(observer: MTMCObserver?) { fun removeObserver(observer: MTMCObserver?) {
observers!!.remove(observer!!) observers.remove(observer!!)
} }
fun pause() { fun pause() {
@@ -936,7 +934,7 @@ class MonTanaMiniComputer {
fun setArg(arg: String) { fun setArg(arg: String) {
if (!arg.isEmpty()) { if (!arg.isEmpty()) {
val start = getRegisterValue(Register.BP) val start = getRegisterValue(Register.BP)
val bytes = arg.toByteArray() val bytes = arg.encodeToByteArray()
writeStringToMemory(start.toInt(), bytes) writeStringToMemory(start.toInt(), bytes)
setRegisterValue(Register.A0, start.toInt()) setRegisterValue(Register.A0, start.toInt())
setRegisterValue(Register.BP, start + bytes.size + 1) setRegisterValue(Register.BP, start + bytes.size + 1)
@@ -953,12 +951,15 @@ class MonTanaMiniComputer {
} }
fun rewind() { fun rewind() {
val latestRewindStep = rewindSteps!!.pop() if (rewindIndex >= 0) {
latestRewindStep.rewind() val latestRewindStep = rewindSteps[rewindIndex]
rewindIndex--
latestRewindStep?.rewind()
}
} }
val isBackAvailable: Boolean val isBackAvailable: Boolean
get() = !rewindSteps!!.isEmpty() get() = rewindIndex >= 0
enum class ComputerStatus { enum class ComputerStatus {
READY, READY,
@@ -1014,17 +1015,8 @@ class MonTanaMiniComputer {
return true return true
} }
val isMcp = getBits(16, 8, instruction) == 5.toShort() val isMcp = getBits(16, 8, instruction) == 5.toShort()
if (isMcp) {
return true
}
return false
}
@JvmStatic return isMcp
fun main(args: Array<String>) {
val computer = MonTanaMiniComputer()
computer.speed = 1 // default to 1hz
computer.start()
} }
} }
} }

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.MonTanaMiniComputer
import mtmc.emulator.Register import mtmc.emulator.Register
import mtmc.os.SysCall.Companion.getValue import mtmc.os.SysCall.Companion.getValue
import mtmc.os.fs.File
import mtmc.os.fs.Files
import mtmc.os.shell.Shell import mtmc.os.shell.Shell
import java.io.File import mtmc.util.currentTimeMillis
import java.io.IOException
import java.nio.file.Files
import java.util.*
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import kotlin.text.Charsets.US_ASCII import kotlin.random.Random
class MTOS(private val computer: MonTanaMiniComputer) { class MTOS(private val computer: MonTanaMiniComputer) {
private var timer: Long = 0 private var timer: Double = 0.0
var random: Random = Random() var random: Random = Random.Default
// Editor support // Editor support
var currentFile: String? = null var currentFile: String? = null
@@ -52,59 +51,59 @@ class MTOS(private val computer: MonTanaMiniComputer) {
fun handleSysCall(syscallNumber: Short) { fun handleSysCall(syscallNumber: Short) {
if (syscallNumber == getValue("exit").toShort()) { if (syscallNumber == getValue("exit").toShort()) {
computer.setStatus(MonTanaMiniComputer.ComputerStatus.FINISHED) computer.setStatus(MonTanaMiniComputer.ComputerStatus.FINISHED)
} else if (syscallNumber == getValue("rint").toShort()) { // } else if (syscallNumber == getValue("rint").toShort()) {
// rint // // rint
if (!computer.console.hasShortValue()) { // if (!computer.console.hasShortValue()) {
computer.notifyOfRequestInteger() // computer.notifyOfRequestInteger()
} // }
while (!computer.console.hasShortValue() && computer.getStatus() == MonTanaMiniComputer.ComputerStatus.EXECUTING) { // while (!computer.console.hasShortValue() && computer.getStatus() == MonTanaMiniComputer.ComputerStatus.EXECUTING) {
try { // try {
Thread.sleep(10) // Thread.sleep(10)
} catch (e: InterruptedException) { // } catch (e: InterruptedException) {
} // }
} // }
val `val` = computer.console.readInt() // val `val` = computer.console.readInt()
computer.setRegisterValue(Register.RV, `val`.toInt()) // computer.setRegisterValue(Register.RV, `val`.toInt())
} else if (syscallNumber == getValue("wint").toShort()) { } else if (syscallNumber == getValue("wint").toShort()) {
// wint // wint
val value = computer.getRegisterValue(Register.A0) val value = computer.getRegisterValue(Register.A0)
computer.console.writeInt(value) computer.console.writeInt(value)
} else if (syscallNumber == getValue("rchr").toShort()) { // } else if (syscallNumber == getValue("rchr").toShort()) {
if (!computer.console.hasShortValue()) { // if (!computer.console.hasShortValue()) {
computer.notifyOfRequestCharacter() // computer.notifyOfRequestCharacter()
} // }
while (!computer.console.hasShortValue() && computer.getStatus() == MonTanaMiniComputer.ComputerStatus.EXECUTING) { // while (!computer.console.hasShortValue() && computer.getStatus() == MonTanaMiniComputer.ComputerStatus.EXECUTING) {
try { // try {
Thread.sleep(10) // Thread.sleep(10)
} catch (e: InterruptedException) { // } catch (e: InterruptedException) {
} // }
} // }
val `val` = computer.console.readChar() // val `val` = computer.console.readChar()
computer.setRegisterValue(Register.RV, `val`.code) // computer.setRegisterValue(Register.RV, `val`.code)
} else if (syscallNumber == getValue("wchr").toShort()) { } else if (syscallNumber == getValue("wchr").toShort()) {
val value = computer.getRegisterValue(Register.A0) val value = computer.getRegisterValue(Register.A0)
computer.console.print("" + Char(value.toUShort())) computer.console.print("" + Char(value.toUShort()))
} else if (syscallNumber == getValue("rstr").toShort()) { // } else if (syscallNumber == getValue("rstr").toShort()) {
// rstr // // rstr
val pointer = computer.getRegisterValue(Register.A0) // val pointer = computer.getRegisterValue(Register.A0)
val maxLen = computer.getRegisterValue(Register.A1) // val maxLen = computer.getRegisterValue(Register.A1)
if (!computer.console.hasReadString()) { // if (!computer.console.hasReadString()) {
computer.notifyOfRequestString() // computer.notifyOfRequestString()
} // }
while (!computer.console.hasReadString() && computer.getStatus() == MonTanaMiniComputer.ComputerStatus.EXECUTING) { // while (!computer.console.hasReadString() && computer.getStatus() == MonTanaMiniComputer.ComputerStatus.EXECUTING) {
try { // try {
Thread.sleep(10) // Thread.sleep(10)
} catch (e: InterruptedException) { // } catch (e: InterruptedException) {
} // }
} // }
val string = computer.console.readString() // val string = computer.console.readString()
val bytes = string!!.toByteArray(US_ASCII) // val bytes = string!!.encodeToByteArray()
val bytesToRead = min(bytes.size, maxLen.toInt()) // val bytesToRead = min(bytes.size, maxLen.toInt())
for (i in 0..<bytesToRead) { // for (i in 0..<bytesToRead) {
val aByte = bytes[i] // val aByte = bytes[i]
computer.writeByteToMemory(pointer + i, aByte) // computer.writeByteToMemory(pointer + i, aByte)
} // }
computer.setRegisterValue(Register.RV, bytesToRead) // computer.setRegisterValue(Register.RV, bytesToRead)
} else if (syscallNumber == getValue("wstr").toShort()) { } else if (syscallNumber == getValue("wstr").toShort()) {
// wstr // wstr
val pointer = computer.getRegisterValue(Register.A0) val pointer = computer.getRegisterValue(Register.A0)
@@ -172,14 +171,14 @@ class MTOS(private val computer: MonTanaMiniComputer) {
} }
computer.setRegisterValue(Register.RV, random.nextInt(low.toInt(), high + 1)) computer.setRegisterValue(Register.RV, random.nextInt(low.toInt(), high + 1))
} else if (syscallNumber == getValue("sleep").toShort()) { // } else if (syscallNumber == getValue("sleep").toShort()) {
// sleep // // sleep
val millis = computer.getRegisterValue(Register.A0) // val millis = computer.getRegisterValue(Register.A0)
try { // try {
if (millis > 0) Thread.sleep(millis.toLong()) // if (millis > 0) Thread.sleep(millis.toLong())
} catch (e: InterruptedException) { // } catch (e: InterruptedException) {
throw RuntimeException(e) // throw RuntimeException(e)
} // }
} else if (syscallNumber == getValue("fbreset").toShort()) { } else if (syscallNumber == getValue("fbreset").toShort()) {
// fbreset // fbreset
computer.display.reset() computer.display.reset()
@@ -241,9 +240,40 @@ class MTOS(private val computer: MonTanaMiniComputer) {
try { try {
// special handling for game-of-life cell files // special handling for game-of-life cell files
if ("cells" == fileType) { if ("cells" == fileType) {
val str = Files.readString(file.toPath()) //val str = Files.readString(file.toPath())
val lines = Arrays val str = """
.stream<String>(str.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) ! galaxy
! Kok's galaxy - a whirling oscillator
.......................................
.......................................
.......................................
.......................................
.......................................
.......................................
.......................................
.......................................
.......................................
.......................................
.......................................
.......................................
.......................................
...............OOOOOO.OO.....
...............OOOOOO.OO
......................OO
...............OO.....OO
...............OO.....OO
...............OO.....OO
...............OO.......
...............OO.OOOOOO
...............OO.OOOOOO.......................................
.......................................
.......................................
.......................................
.......................................
.......................................
""".trimIndent()
val lines: List<String> =
str.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
.filter { s: String? -> !s!!.startsWith("!") } .filter { s: String? -> !s!!.startsWith("!") }
.toList() .toList()
@@ -251,7 +281,7 @@ class MTOS(private val computer: MonTanaMiniComputer) {
val cappedLines = min(linesTotal, maxSize2.toInt()) val cappedLines = min(linesTotal, maxSize2.toInt())
for (lineNum in 0..<cappedLines) { for (lineNum in 0..<cappedLines) {
val line = lines.get(lineNum) val line: String = lines.get(lineNum)
for (colNum in 0..<maxSize1) { for (colNum in 0..<maxSize1) {
val offset = lineNum * maxSize1 + colNum val offset = lineNum * maxSize1 + colNum
val byteOffset = offset / 8 val byteOffset = offset / 8
@@ -275,7 +305,7 @@ class MTOS(private val computer: MonTanaMiniComputer) {
} }
} }
computer.setRegisterValue(Register.RV, 0) computer.setRegisterValue(Register.RV, 0)
} catch (e: IOException) { } catch (e: Exception) {
computer.setRegisterValue(Register.RV, -1) computer.setRegisterValue(Register.RV, -1)
e.printStackTrace() // debugging e.printStackTrace() // debugging
} }
@@ -290,7 +320,6 @@ class MTOS(private val computer: MonTanaMiniComputer) {
computer.writeByteToMemory(destination + i, aByte) computer.writeByteToMemory(destination + i, aByte)
} }
//TODO: Should this return the length with or without the null terminator? //TODO: Should this return the length with or without the null terminator?
computer.writeByteToMemory(destination + maxSize - 1, 0.toByte()) computer.writeByteToMemory(destination + maxSize - 1, 0.toByte())
computer.setRegisterValue(Register.RV, maxSize - 1) computer.setRegisterValue(Register.RV, maxSize - 1)
@@ -300,18 +329,18 @@ class MTOS(private val computer: MonTanaMiniComputer) {
if (computer.fileSystem.exists(dir)) { if (computer.fileSystem.exists(dir)) {
computer.setRegisterValue(Register.RV, 0) computer.setRegisterValue(Register.RV, 0)
computer.fileSystem.setCWD(dir) computer.fileSystem.cWD = dir
} else { } else {
computer.setRegisterValue(Register.RV, 1) computer.setRegisterValue(Register.RV, 1)
} }
} else if (syscallNumber == getValue("timer").toShort()) { } else if (syscallNumber == getValue("timer").toShort()) {
val value = computer.getRegisterValue(Register.A0) val value = computer.getRegisterValue(Register.A0)
if (value > 0) this.timer = System.currentTimeMillis() + value if (value > 0) this.timer = currentTimeMillis() + value
computer.setRegisterValue( computer.setRegisterValue(
Register.RV, Register.RV,
max(0, this.timer - System.currentTimeMillis()).toInt() max(0.0, this.timer - currentTimeMillis()).toInt()
) )
} else if (syscallNumber == getValue("drawimg").toShort()) { } else if (syscallNumber == getValue("drawimg").toShort()) {
val image = computer.getRegisterValue(Register.A0) val image = computer.getRegisterValue(Register.A0)
@@ -401,10 +430,13 @@ class MTOS(private val computer: MonTanaMiniComputer) {
} }
val file = list[offset.toInt()] val file = list[offset.toInt()]
val name = file.getName() val name = file.name
val size = min(maxSizeOut - 1, name.length) val size = min(maxSizeOut - 1, name.length)
computer.writeWordToMemory(destination.toInt(), if (file.isDirectory()) 1 else 0) computer.writeWordToMemory(
destination.toInt(),
if (file.directory) 1 else 0
)
for (i in 0..<size) { for (i in 0..<size) {
val aByte = name.get(i).code.toByte() val aByte = name.get(i).code.toByte()
@@ -433,9 +465,9 @@ class MTOS(private val computer: MonTanaMiniComputer) {
length++ length++
} }
try { try {
val outputString = String(computer.memory, pointer.toInt(), length.toInt(), US_ASCII) val outputString = computer.memory.decodeToString(pointer.toInt(), pointer.toInt() + length)
return outputString return outputString
} catch (ignored: StringIndexOutOfBoundsException) { } catch (ignored: IndexOutOfBoundsException) {
computer.setStatus(MonTanaMiniComputer.ComputerStatus.PERMANENT_ERROR) computer.setStatus(MonTanaMiniComputer.ComputerStatus.PERMANENT_ERROR)
return "" return ""
} }
@@ -447,9 +479,12 @@ class MTOS(private val computer: MonTanaMiniComputer) {
} }
} }
fun loadFile(path: String?): File { fun loadFile(path: String?): File = File(path ?: "<empty>")
val fs = computer.fileSystem /*
val file = fs.getRealPath(path).toFile() {
return file val fs = computer.fileSystem
} val file = fs.getRealPath(path).toFile()
return file
}
*/
} }

View File

@@ -1,7 +1,5 @@
package mtmc.os package mtmc.os
import java.util.*
enum class SysCall(value: Int) { enum class SysCall(value: Int) {
EXIT(0x00), EXIT(0x00),
RINT(0x01), RINT(0x01),
@@ -50,25 +48,24 @@ enum class SysCall(value: Int) {
companion object { companion object {
fun isSysCall(call: String): Boolean { fun isSysCall(call: String): Boolean {
try { try {
valueOf(call.uppercase(Locale.getDefault())) valueOf(call.uppercase())
return true return true
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
return false return false
} }
} }
@JvmStatic
fun getValue(call: String): Byte { fun getValue(call: String): Byte {
return valueOf(call.uppercase(Locale.getDefault())).value return valueOf(call.uppercase()).value
} }
fun getString(syscallCode: Byte): String? { fun getString(syscallCode: Byte): String? {
for (o in entries) { for (o in entries) {
if (o.value == syscallCode) { if (o.value == syscallCode) {
return o.name.lowercase(Locale.getDefault()) return o.name.lowercase()
} }
} }
return null return null
} }
} }
} }

View File

@@ -0,0 +1,58 @@
package mtmc.os.exec
import mtmc.emulator.DebugInfo
import mtmc.os.fs.Path
data class Executable(
val format: Format,
val code: ByteArray,
val data: ByteArray,
val graphics: Array<ByteArray>,
val sourceName: String,
val debugInfo: DebugInfo?
) {
enum class Format(val formatName: String) {
Orc1("orc1");
}
fun dump(): String? {
return null //Gson().toJson(this)
}
fun dump(path: Path) {
/*
FileWriter(path.toFile()).use { fw ->
dump(fw)
}
*/
}
/*
fun dump(writer: Writer?) {
val gson = Gson()
gson.toJson(this, writer)
}
*/
companion object {
fun load(exe: String?): Executable? {
return null //Gson().fromJson<Executable?>(exe, Executable::class.java)
}
fun load(path: Path): Executable? = null
/*
{
FileReader(path.toFile()).use { fw ->
return load(fw)
}
}
*/
/*
fun load(reader: Reader): Executable? {
val gson = Gson()
return gson.fromJson<Executable?>(reader, Executable::class.java)
}
*/
}
}

View File

@@ -0,0 +1,11 @@
package mtmc.os.fs
object System {
val console: Console = Console()
}
class Console {
fun readLine(prompt: String? = ""): String {
TODO("Not yet implemented")
}
}

View File

@@ -0,0 +1,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.MTMCDisplay
import mtmc.emulator.MonTanaMiniComputer import mtmc.emulator.MonTanaMiniComputer
import mtmc.os.fs.File
import mtmc.os.shell.ShellCommand import mtmc.os.shell.ShellCommand
import mtmc.tokenizer.MTMCToken import mtmc.tokenizer.MTMCToken
import mtmc.tokenizer.MTMCTokenizer import mtmc.tokenizer.MTMCTokenizer
import java.io.File import kotlin.random.Random
import java.util.*
import javax.imageio.ImageIO
class DisplayCommand : ShellCommand() { class DisplayCommand : ShellCommand() {
var random: Random = Random() var random: Random = Random.Default
@Throws(Exception::class) @Throws(Exception::class)
public override fun exec(tokens: MTMCTokenizer, computer: MonTanaMiniComputer) { public override fun exec(tokens: MTMCTokenizer, computer: MonTanaMiniComputer) {
@@ -42,8 +41,8 @@ class DisplayCommand : ShellCommand() {
if (tokens.more()) { if (tokens.more()) {
val imagePath = tokens.collapseTokensAsString() val imagePath = tokens.collapseTokensAsString()
val file = computer.oS.loadFile(imagePath) val file = computer.oS.loadFile(imagePath)
val img = ImageIO.read(file) //val img = ImageIO.read(file)
computer.display.loadScaledImage(img) //computer.display.loadScaledImage(img)
} else { } else {
usageException() usageException()
} }
@@ -54,11 +53,15 @@ class DisplayCommand : ShellCommand() {
} else if (tokens.match(MTMCToken.TokenType.INTEGER)) { } else if (tokens.match(MTMCToken.TokenType.INTEGER)) {
val row = tokens.consumeAsInteger() val row = tokens.consumeAsInteger()
val col = tokens.require( val col = tokens.require(
mtmc.tokenizer.MTMCToken.TokenType.INTEGER, mtmc.tokenizer.MTMCToken.TokenType.INTEGER
java.lang.Runnable { this.usageException() })!!.intValue() ) {
this.usageException()
}!!.intValue()
val color = tokens.require( val color = tokens.require(
mtmc.tokenizer.MTMCToken.TokenType.INTEGER, mtmc.tokenizer.MTMCToken.TokenType.INTEGER
java.lang.Runnable { this.usageException() })!!.intValue() ) {
this.usageException()
}!!.intValue()
computer.display.setPixel(row, col, color) computer.display.setPixel(row, col, color)
} else { } else {
usageException() usageException()

View File

@@ -7,7 +7,7 @@ import mtmc.tokenizer.MTMCTokenizer
class ExitCommand : ShellCommand() { class ExitCommand : ShellCommand() {
public override fun exec(tokens: MTMCTokenizer, computer: MonTanaMiniComputer) { public override fun exec(tokens: MTMCTokenizer, computer: MonTanaMiniComputer) {
computer.console.println("Goodbye!") computer.console.println("Goodbye!")
System.exit(1) //System.exit(1)
} }
override val help: String override val help: String

View File

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

View File

@@ -3,13 +3,13 @@ package mtmc.os.shell.builtins
import mtmc.emulator.MonTanaMiniComputer import mtmc.emulator.MonTanaMiniComputer
import mtmc.os.exec.Executable.Companion.load import mtmc.os.exec.Executable.Companion.load
import mtmc.os.fs.FileSystem import mtmc.os.fs.FileSystem
import mtmc.os.fs.Path
import mtmc.os.shell.ShellCommand import mtmc.os.shell.ShellCommand
import mtmc.tokenizer.MTMCTokenizer import mtmc.tokenizer.MTMCTokenizer
import java.nio.file.Path
class LoadCommand : ShellCommand() { class LoadCommand : ShellCommand() {
@Throws(Exception::class) @Throws(Exception::class)
public override fun exec(tokens: MTMCTokenizer, computer: MonTanaMiniComputer) { override fun exec(tokens: MTMCTokenizer, computer: MonTanaMiniComputer) {
val fs = computer.fileSystem val fs = computer.fileSystem
val program = tokens.collapseTokensAsString() val program = tokens.collapseTokensAsString()
require(!(program == null || program.isBlank())) { "missing or required argument 'src'" } require(!(program == null || program.isBlank())) { "missing or required argument 'src'" }

View File

@@ -1,10 +1,8 @@
package mtmc.os.shell.builtins package mtmc.os.shell.builtins
import mtmc.emulator.MonTanaMiniComputer import mtmc.emulator.MonTanaMiniComputer
import mtmc.lang.sea.SeaLanguage
import mtmc.os.shell.ShellCommand import mtmc.os.shell.ShellCommand
import mtmc.tokenizer.MTMCTokenizer import mtmc.tokenizer.MTMCTokenizer
import mtmc.util.StringEscapeUtils.escapeString
class SeacCommand : ShellCommand() { class SeacCommand : ShellCommand() {
@Throws(Exception::class) @Throws(Exception::class)
@@ -24,15 +22,18 @@ class SeacCommand : ShellCommand() {
requireNotNull(filename) { "expected source file" } requireNotNull(filename) { "expected source file" }
require(fs.exists(filename)) { "file " + escapeString(filename) + " does not exist" } require(fs.exists(filename)) { "file " + filename + " does not exist" }
println(fs.resolve(filename)) println(fs.resolve(filename))
val lang = SeaLanguage()
val content = fs.readFile(filename)
val exec = lang.compileExecutable(fs.resolve(filename), content)
val bin = exec.dump() /*
computer.fileSystem.writeFile(output, bin) val lang = SeaLanguage()
computer.notifyOfFileSystemUpdate() val content = fs.readFile(filename)
val exec = lang.compileExecutable(fs.resolve(filename), content)
val bin = exec.dump()
computer.fileSystem.writeFile(output, bin)
computer.notifyOfFileSystemUpdate()
*/
} }
override val help: String override val help: String

View File

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

View File

@@ -4,13 +4,10 @@ import mtmc.emulator.MonTanaMiniComputer
import mtmc.os.shell.ShellCommand import mtmc.os.shell.ShellCommand
import mtmc.tokenizer.MTMCToken import mtmc.tokenizer.MTMCToken
import mtmc.tokenizer.MTMCTokenizer import mtmc.tokenizer.MTMCTokenizer
import java.util.*
class SpeedCommand : ShellCommand() { class SpeedCommand : ShellCommand() {
private val speeds: MutableList<Int?> = Arrays.asList<Int?>( private val speeds = listOf(
*arrayOf<Int>(
1, 10, 100, 1000, 10000, 100000, 1000000 1, 10, 100, 1000, 10000, 100000, 1000000
)
) )
@Throws(Exception::class) @Throws(Exception::class)

View File

@@ -1,14 +1,16 @@
package mtmc.tokenizer package mtmc.tokenizer
import java.util.* fun Char.isHexDigit(): Boolean {
return this in '0'..'9' || this in 'a'..'f' || this in 'A'..'F'
}
class MTMCScanner(private val src: String, private val lineCommentStart: String) { class MTMCScanner(private val src: String, private val lineCommentStart: String) {
var position: Int = 0 var position: Int = 0
var line: Int = 1 var line: Int = 1
var lineOffset: Int = 0 var lineOffset: Int = 0
var tokens: LinkedList<MTMCToken> = LinkedList<MTMCToken>() var tokens = mutableListOf<MTMCToken>()
fun tokenize(): LinkedList<MTMCToken> { fun tokenize(): MutableList<MTMCToken> {
consumeWhitespace() consumeWhitespace()
while (!scanEnd()) { while (!scanEnd()) {
scanToken() scanToken()
@@ -38,7 +40,7 @@ class MTMCScanner(private val src: String, private val lineCommentStart: String)
} }
private fun scanLineComment(): Boolean { private fun scanLineComment(): Boolean {
val bytes = lineCommentStart.toByteArray() val bytes = lineCommentStart.encodeToByteArray()
for (i in bytes.indices) { for (i in bytes.indices) {
val aChar = bytes[i] val aChar = bytes[i]
if (peek(i).code.toByte() != aChar) { if (peek(i).code.toByte() != aChar) {
@@ -188,7 +190,7 @@ class MTMCScanner(private val src: String, private val lineCommentStart: String)
private fun scanHex(start: Int): Boolean { private fun scanHex(start: Int): Boolean {
takeChar() // take leading zero takeChar() // take leading zero
takeChar() // take 'x' takeChar() // take 'x'
while (HexFormat.isHexDigit(peek().code)) { while (peek().isHexDigit()) {
takeChar() takeChar()
} }
tokens.add(makeToken(MTMCToken.TokenType.HEX, strValueFrom(start), start)) tokens.add(makeToken(MTMCToken.TokenType.HEX, strValueFrom(start), start))

View File

@@ -1,22 +1,17 @@
package mtmc.tokenizer package mtmc.tokenizer
@JvmRecord
data class MTMCToken( data class MTMCToken(
@JvmField val start: Int, val start: Int,
@JvmField val end: Int, val end: Int,
val line: Int, val line: Int,
val lineOffset: Int, val lineOffset: Int,
@JvmField val stringValue: String, val stringValue: String,
@JvmField val type: TokenType? val type: TokenType?
) { ) {
override fun toString(): String { override fun toString(): String {
return stringValue return stringValue
} }
fun stringValue(): String {
return stringValue
}
fun charValue(): Char { fun charValue(): Char {
return stringValue.get(0) return stringValue.get(0)
} }

View File

@@ -1,14 +1,13 @@
package mtmc.tokenizer package mtmc.tokenizer
import java.util.* import mtmc.util.Runnable
import java.util.stream.Stream
import kotlin.math.max import kotlin.math.max
class MTMCTokenizer(var source: String, lineCommentStart: String) { class MTMCTokenizer(var source: String, lineCommentStart: String) {
var tokens: LinkedList<MTMCToken> = MTMCScanner(source, lineCommentStart).tokenize() var tokens: MutableList<MTMCToken> = MTMCScanner(source, lineCommentStart).tokenize()
var currentToken: Int = 0 var currentToken: Int = 0
fun currentToken(): MTMCToken { fun getCurrentToken(): MTMCToken {
return tokens.get(currentToken) return tokens.get(currentToken)
} }
@@ -29,8 +28,8 @@ class MTMCTokenizer(var source: String, lineCommentStart: String) {
} }
fun matchAndConsume(identifier: String?): Boolean { fun matchAndConsume(identifier: String?): Boolean {
if (currentToken().type == MTMCToken.TokenType.IDENTIFIER && if (getCurrentToken().type == MTMCToken.TokenType.IDENTIFIER &&
currentToken().stringValue == identifier getCurrentToken().stringValue == identifier
) { ) {
return true return true
} else { } else {
@@ -40,7 +39,7 @@ class MTMCTokenizer(var source: String, lineCommentStart: String) {
fun match(vararg type: MTMCToken.TokenType?): Boolean { fun match(vararg type: MTMCToken.TokenType?): Boolean {
for (tokenType in type) { for (tokenType in type) {
if (currentToken().type == tokenType) { if (getCurrentToken().type == tokenType) {
return true return true
} }
} }
@@ -52,17 +51,13 @@ class MTMCTokenizer(var source: String, lineCommentStart: String) {
} }
fun more(): Boolean { fun more(): Boolean {
return currentToken().type != MTMCToken.TokenType.EOF return getCurrentToken().type != MTMCToken.TokenType.EOF
} }
fun previousToken(): MTMCToken? { fun previousToken(): MTMCToken? {
return tokens.get(max(0, currentToken - 1)) return tokens.get(max(0, currentToken - 1))
} }
fun stream(): Stream<MTMCToken?> {
return tokens.stream()
}
override fun toString(): String { override fun toString(): String {
val sb = StringBuilder() val sb = StringBuilder()
for (i in tokens.indices) { for (i in tokens.indices) {
@@ -88,7 +83,7 @@ class MTMCTokenizer(var source: String, lineCommentStart: String) {
do { do {
last = consume() last = consume()
sb.append(last.stringValue) sb.append(last.stringValue)
next = currentToken() next = getCurrentToken()
} while (more() && last.end == next.start) } while (more() && last.end == next.start)
return sb.toString() return sb.toString()
} else { } else {
@@ -104,7 +99,7 @@ class MTMCTokenizer(var source: String, lineCommentStart: String) {
if (match(tokenType)) { if (match(tokenType)) {
return consume() return consume()
} else { } else {
notFound.run() notFound.invoke()
return null return null
} }
} }

View File

@@ -11,38 +11,28 @@ object BinaryUtils {
return 0 return 0
} }
val returnValue = (value.toInt() and 0xffff) ushr (start - totalBits) val returnValue = (value.toInt() and 0xffff) ushr (start - totalBits)
var mask = 0 var mask = 1
var toShift = totalBits mask = mask shl totalBits
while (toShift > 0) { mask--
toShift--
mask = mask shl 1
mask = mask + 1
}
return (returnValue and mask).toShort() return (returnValue and mask).toShort()
} }
fun toBinary(aByte: Byte): String { fun toBinary(aByte: Byte): String {
val binaryString = Integer.toBinaryString(aByte.toInt()) val zeroed = aByte.toInt().toString(2).padStart(8, '0')
val formatted = String.format("%8s", binaryString)
val zeroed = formatted.replace(" ".toRegex(), "0")
val underScored = zeroed.replace("....".toRegex(), "$0_") val underScored = zeroed.replace("....".toRegex(), "$0_")
val noTrailingUnderscore = underScored.substring(0, underScored.length - 1) val noTrailingUnderscore = underScored.substring(0, underScored.length - 1)
return "0b" + noTrailingUnderscore return "0b" + noTrailingUnderscore
} }
fun toBinary(aShort: Short): String { fun toBinary(aShort: Short): String {
val binaryString = Integer.toBinaryString(aShort.toInt()) val zeroed = aShort.toInt().toString(2).padStart(16, '0')
val formatted = String.format("%16s", binaryString)
val zeroed = formatted.replace(" ".toRegex(), "0")
val underScored = zeroed.replace("....".toRegex(), "$0_") val underScored = zeroed.replace("....".toRegex(), "$0_")
val noTrailingUnderscore = underScored.substring(0, underScored.length - 1) val noTrailingUnderscore = underScored.substring(0, underScored.length - 1)
return "0b" + noTrailingUnderscore return "0b" + noTrailingUnderscore
} }
fun toBinary(anInt: Int): String { fun toBinary(anInt: Int): String {
val binaryString = Integer.toBinaryString(anInt) val zeroed = anInt.toString(2).padStart(32, '0')
val formatted = String.format("%32s", binaryString)
val zeroed = formatted.replace(" ".toRegex(), "0")
val underScored = zeroed.replace("....".toRegex(), "$0_") val underScored = zeroed.replace("....".toRegex(), "$0_")
val noTrailingUnderscore = underScored.substring(0, underScored.length - 1) val noTrailingUnderscore = underScored.substring(0, underScored.length - 1)
return "0b" + noTrailingUnderscore return "0b" + noTrailingUnderscore

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

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