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

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

View File

@@ -1,19 +0,0 @@
package mtmc.asm
import mtmc.tokenizer.MTMCToken
abstract class ASMElement(
val labels: List<MTMCToken>,
@JvmField var lineNumber: Int
) : HasLocation {
@JvmField
var errors: MutableList<ASMError> = ArrayList()
override var location: Int = 0
override var sizeInBytes: Int = 0
fun addError(token: MTMCToken, error: String) {
errors.add(ASMError(token, error))
}
abstract fun addError(integerValueRequired: String)
}

View File

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

View File

@@ -1,798 +0,0 @@
package mtmc.asm
import mtmc.asm.data.Data
import mtmc.asm.graphics.Graphic
import mtmc.asm.instructions.ALUInstruction
import mtmc.asm.instructions.ALUOp.Companion.isALUOp
import mtmc.asm.instructions.ErrorInstruction
import mtmc.asm.instructions.Instruction
import mtmc.asm.instructions.InstructionType
import mtmc.asm.instructions.InstructionType.Companion.fromString
import mtmc.asm.instructions.InstructionType.InstructionClass
import mtmc.asm.instructions.JumpInstruction
import mtmc.asm.instructions.JumpRegisterInstruction
import mtmc.asm.instructions.LoadStoreInstruction
import mtmc.asm.instructions.LoadStoreRegisterInstruction
import mtmc.asm.instructions.MetaInstruction
import mtmc.asm.instructions.MiscInstruction
import mtmc.asm.instructions.StackInstruction
import mtmc.asm.instructions.TestInstruction
import mtmc.emulator.DebugInfo
import mtmc.emulator.DebugInfo.GlobalInfo
import mtmc.emulator.DebugInfo.LocalInfo
import mtmc.emulator.MonTanaMiniComputer
import mtmc.emulator.Register.Companion.isReadable
import mtmc.emulator.Register.Companion.isWriteable
import mtmc.os.SysCall
import mtmc.os.exec.Executable
import mtmc.tokenizer.MTMCToken
import mtmc.tokenizer.MTMCTokenizer
import java.io.File
import java.nio.charset.StandardCharsets
import java.util.*
import java.util.stream.Collectors
class Assembler {
var instructions: MutableList<Instruction> = ArrayList<Instruction>()
var instructionsSizeInBytes: Int = 0
var data: MutableList<Data> = ArrayList<Data>()
var graphics: MutableList<Graphic> = ArrayList<Graphic>()
var dataSize: Int = 0
var labels: HashMap<String?, HasLocation?>
private var mode: ASMMode = ASMMode.TEXT
var tokenizer: MTMCTokenizer? = null
private var lastLabels: MutableList<MTMCToken> = mutableListOf()
private var srcName = "disk/file.asm"
private var debugStrings: MutableList<String> = mutableListOf()
init {
labels = HashMap<String?, HasLocation?>()
}
fun assemble(asm: String): AssemblyResult {
return assemble(null, asm)
}
fun assemble(file: String?, asm: String): AssemblyResult {
tokenizer = MTMCTokenizer(asm, "#")
debugStrings = ArrayList<String>()
parseAssembly()
resolveLocations()
val errors = collectErrors()
var debugInfo: DebugInfo? = null
var code: ByteArray = ByteArray(0)
var data: ByteArray = ByteArray(0)
var graphics: Array<ByteArray> = arrayOf()
if (errors.isEmpty()) {
code = genCode()
data = genData()
graphics = genGraphics()
debugInfo = genDebugInfo(file, asm, code)
}
return AssemblyResult(code, data, graphics, debugInfo, errors)
}
private fun genDebugInfo(
assemblyFile: String?,
assemblySource: String,
code: ByteArray
): DebugInfo {
val assemblyLineNumbers = IntArray(code.size)
var originalFile: String = ""
val originalLineNumbers = IntArray(code.size)
val globals: MutableList<GlobalInfo> = ArrayList<GlobalInfo>()
val locals = Array<Array<LocalInfo?>>(code.size) { arrayOf() }
var location = 0
var originalLineNumber = 0
val currentLocals: MutableMap<String?, LocalInfo?> = TreeMap<String?, LocalInfo?>()
for (instruction in instructions) {
val asmLineNumber = instruction.lineNumber
if (instruction is MetaInstruction) {
if (instruction.isFileDirective) {
originalFile = instruction.getOriginalFilePath()!!
} else if (instruction.isLineDirective) {
originalLineNumber = instruction.originalLineNumber
} else if (instruction.isGlobalDirective) {
globals.add(
GlobalInfo(
instruction.getGlobalName(),
instruction.getGlobalLocation(),
instruction.getGlobalType()
)
)
} else if (instruction.isLocalDirective) {
currentLocals.put(
instruction.getLocalName(),
LocalInfo(
instruction.getLocalName(),
instruction.getLocalOffset(),
instruction.getLocalType()
)
)
} else if (instruction.isEndLocalDirective) {
currentLocals.remove(instruction.getLocalName())
}
}
while (location < instruction.location + instruction.sizeInBytes) {
assemblyLineNumbers[location] = asmLineNumber
originalLineNumbers[location] = originalLineNumber
locals[location] = currentLocals.values.toTypedArray<LocalInfo?>()
location++
}
}
val debugInfo = DebugInfo(
debugStrings,
assemblyFile,
assemblySource,
assemblyLineNumbers,
originalFile,
originalLineNumbers,
globals.toTypedArray<GlobalInfo>(),
locals
)
return debugInfo
}
fun assembleExecutable(srcName: String, asm: String): Executable {
this.srcName = "disk/" + srcName //TODO: The prefix should be replaced with FileSystem access
val result = assemble(srcName, asm)
if (!result.errors.isEmpty()) {
throw RuntimeException(
"Errors:\n" + result.errors
.stream()
.map<String> { e: ASMError? -> " - " + e!!.formattedErrorMessage() }
.collect(Collectors.joining("\n")))
}
return Executable(
Executable.Format.Orc1,
result.code,
result.data,
result.graphics,
srcName,
result.debugInfo
)
}
private fun parseAssembly() {
while (tokenizer!!.more()) {
parseLine()
}
}
private fun genData(): ByteArray {
val dataBytes = ByteArray(dataSize)
for (dataElt in data) {
dataElt.genData(dataBytes, this)
}
return dataBytes
}
private fun genCode(): ByteArray {
val code = ByteArray(this.instructionsSizeInBytes)
for (instruction in instructions) {
instruction.genCode(code, this)
}
return code
}
private fun genOriginalLineNumbers(length: Int): IntArray {
val asmLineNumbers = IntArray(length)
var currentLineNumber = 0
var location = 0
for (instruction in instructions) {
if (instruction is MetaInstruction && instruction.isLineDirective) {
currentLineNumber = instruction.originalLineNumber
}
while (location < instruction.location + instruction.sizeInBytes) {
asmLineNumbers[location] = currentLineNumber
location++
}
}
return asmLineNumbers
}
private fun genGraphics(): Array<ByteArray> {
val graphics = Array<ByteArray>(this.graphics.size) { index ->
graphics[index].imageData
}
return graphics
}
private fun collectErrors(): MutableList<ASMError> {
val errors: MutableList<ASMError> = ArrayList<ASMError>()
for (instruction in instructions) {
errors.addAll(instruction.errors)
}
for (data in this.data) {
errors.addAll(data.errors)
}
for (graphic in this.graphics) {
errors.addAll(graphic.errors)
}
return errors
}
private fun resolveLocations() {
// assign locations
var offset = 0
for (instruction in instructions) {
instruction.location = offset
offset += instruction.sizeInBytes
this.instructionsSizeInBytes += instruction.sizeInBytes
}
for (data in data) {
data.location = offset
offset += data.sizeInBytes
dataSize += data.sizeInBytes
}
for (instruction in instructions) {
instruction.validateLabel(this)
}
}
private fun parseLine() {
val tokens = this.tokensForLine
if (parseMetaDirective(tokens)) {
return
}
val newMode = parseModeFlag(tokens)
if (newMode != null) {
mode = newMode
return
}
// label only lines are ok and propagate to the next instruction
val labelTokens: MutableList<MTMCToken> = maybeGetLabels(tokens)
val labels = ArrayList<MTMCToken>(lastLabels)
labels.addAll(labelTokens)
lastLabels = labels
if (tokens.isEmpty()) {
for (labelToken in labelTokens) {
val labelData = Data(labelTokens, labelToken.line)
if (hasLabel(labelToken.stringValue)) {
labelData.addError(tokens.poll(), "Label already defined: " + labelToken.stringValue)
} else {
this.labels.put(labelToken.labelValue(), labelData)
}
data.add(labelData)
}
} else if (mode == ASMMode.TEXT) {
parseInstruction(tokens, labels)
} else {
parseData(tokens, labels)
}
}
private fun parseMetaDirective(tokens: LinkedList<MTMCToken>): Boolean {
if (tokens.size >= 2 && tokens.get(0).type == MTMCToken.TokenType.AT) {
tokens.removeFirst()
val metaInstruction = MetaInstruction(tokens.removeFirst())
if (metaInstruction.isFileDirective) {
val path = requireString(tokens, metaInstruction)
metaInstruction.setOriginalFilePath(path)
} else if (metaInstruction.isGlobalDirective) {
val name = requireString(tokens, metaInstruction)
val location = requireIntegerToken(tokens, metaInstruction, Int.Companion.MAX_VALUE)
val type = requireString(tokens, metaInstruction)
metaInstruction.setGlobalInfo(name, location, type)
} else if (metaInstruction.isLocalDirective) {
val name = requireString(tokens, metaInstruction)
val offset = requireIntegerToken(tokens, metaInstruction, Int.Companion.MAX_VALUE)
val type = requireString(tokens, metaInstruction)
metaInstruction.setLocalInfo(name, offset, type)
} else if (metaInstruction.isEndLocalDirective) {
val name = requireString(tokens, metaInstruction)
metaInstruction.setEndLocalInfo(name)
} else if (metaInstruction.isLineDirective) {
val lineNumber = requireIntegerToken(tokens, metaInstruction, Int.Companion.MAX_VALUE)
metaInstruction.setOriginalLineNumber(lineNumber)
} else {
metaInstruction.addError("Unknown meta directive")
}
instructions.add(metaInstruction)
return true
}
return false
}
private fun parseModeFlag(tokens: LinkedList<MTMCToken>): ASMMode? {
if (tokens.size >= 2 && tokens.get(0).type == MTMCToken.TokenType.DOT && tokens.get(1).type == MTMCToken.TokenType.IDENTIFIER && tokens.get(
0
).end == tokens.get(1).start && (tokens.get(1).stringValue == "data" ||
tokens.get(1).stringValue == "text")
) {
return ASMMode.valueOf(tokens.get(1).stringValue.uppercase(Locale.getDefault()))
}
return null
}
private fun parseData(tokens: LinkedList<MTMCToken>, labelTokens: MutableList<MTMCToken>) {
lastLabels = mutableListOf()
var dataToken = tokens.poll()
var dataElt = Data(labelTokens, if (dataToken == null) 0 else dataToken.line)
if (dataToken != null) {
if (dataToken.type == MTMCToken.TokenType.STRING) {
val stringBytes = dataToken.stringValue.toByteArray(StandardCharsets.US_ASCII)
val nullTerminated = ByteArray(stringBytes.size + 1)
System.arraycopy(stringBytes, 0, nullTerminated, 0, stringBytes.size)
nullTerminated[stringBytes.size] = '\u0000'.code.toByte()
dataElt.setValue(dataToken, nullTerminated)
} else if (isInteger(dataToken)) {
val integerValue = dataToken.intValue()
if (integerValue > Short.Companion.MAX_VALUE || integerValue < Short.Companion.MIN_VALUE) {
dataElt.addError(dataToken, "Number is too large")
}
dataElt.setValue(
dataToken,
byteArrayOf((integerValue ushr 8).toByte(), integerValue.toByte())
)
} else if (dataToken.type == MTMCToken.TokenType.DOT) {
dataToken = tokens.poll()
dataElt = Data(labelTokens, dataToken.line)
if (dataToken.stringValue == "int") {
val intToken = requireIntegerToken(tokens, dataElt, MonTanaMiniComputer.MEMORY_SIZE)
if (intToken != null) {
dataElt.setValue(intToken, ByteArray(intToken.intValue() * 2))
}
} else if (dataToken.stringValue == "byte") {
val intToken = requireIntegerToken(tokens, dataElt, MonTanaMiniComputer.MEMORY_SIZE)
if (intToken != null) {
dataElt.setValue(intToken, ByteArray(intToken.intValue()))
}
} else if (dataToken.stringValue == "image") {
val stringToken = requireString(tokens, dataElt)
if (stringToken != null) {
loadGraphic(labelTokens, dataElt, stringToken)
}
} else {
dataElt.addError(dataToken, "only data types are .int, .byte, and .image")
}
} else if (dataToken.type == MTMCToken.TokenType.MINUS) {
val nextToken = tokens.poll() // get next
if (nextToken == null || (nextToken.type != MTMCToken.TokenType.INTEGER && nextToken.type != MTMCToken.TokenType.HEX && nextToken.type != MTMCToken.TokenType.BINARY)) {
dataElt.addError(dataToken, "Number is too negative")
} else {
val integerValue = -1 * nextToken.intValue()
if (integerValue < Short.Companion.MIN_VALUE) {
dataElt.addError(dataToken, "Number is too negative")
}
val joinToken = MTMCToken.join(dataToken, nextToken, MTMCToken.TokenType.INTEGER)
dataElt.setValue(
joinToken,
byteArrayOf((integerValue ushr 8).toByte(), integerValue.toByte())
)
}
} else {
dataElt.addError(dataToken, "Unknown token type: " + dataToken.toString())
}
}
for (labelToken in labelTokens) {
if (hasLabel(labelToken.stringValue)) {
dataElt.addError(tokens.poll(), "Label already defined: " + labelToken.stringValue)
} else {
labels.put(labelToken.labelValue(), dataElt)
}
}
data.add(dataElt)
}
private fun loadGraphic(
labelTokens: MutableList<MTMCToken>,
data: Data,
token: MTMCToken
): Graphic {
val graphic = Graphic(labelTokens, token.line)
val filename = token.stringValue
val file = File(File(this.srcName).getParent(), filename)
val index = graphics.size
data.setValue(token, byteArrayOf(((index shr 8) and 0xFF).toByte(), (index and 0xFF).toByte()))
graphic.setImage(file.getPath())
graphics.add(graphic)
return graphic
}
private fun parseInstruction(tokens: LinkedList<MTMCToken>, labelTokens: MutableList<MTMCToken>) {
var tokens = tokens
var instructionToken = tokens.peekFirst()
if (instructionToken == null) return
lastLabels = mutableListOf()
if (instructionToken.type != MTMCToken.TokenType.IDENTIFIER) {
instructions.add(ErrorInstruction(labelTokens, instructionToken, "Invalid Token"))
return
}
tokens = transformSyntheticInstructions(tokens)
instructionToken = tokens.poll()
val type = fromString(instructionToken.stringValue)
if (type == null) {
instructions.add(
ErrorInstruction(
labelTokens,
instructionToken,
"Unknown instruction token type: " + instructionToken.stringValue
)
)
return
}
val instruction: Instruction?
if (type.instructionClass == InstructionClass.MISC) {
val miscInst = MiscInstruction(type, labelTokens, instructionToken)
if (type == InstructionType.SYS) {
val sysCallType = requireSysCall(tokens, miscInst)
miscInst.setSyscallType(sysCallType)
} else if (type == InstructionType.MOV) {
val toRegister = requireWriteableRegister(tokens, miscInst)
miscInst.setTo(toRegister)
val fromRegister = requireReadableRegister(tokens, miscInst)
miscInst.setFrom(fromRegister)
} else if (type == InstructionType.INC || type == InstructionType.DEC) {
val toRegister = requireWriteableRegister(tokens, miscInst)
miscInst.setTo(toRegister)
if (!tokens.isEmpty()) {
val value = requireIntegerToken(tokens, miscInst, 15)
miscInst.setValue(value)
}
} else if (type == InstructionType.SETI) {
val toRegister = requireWriteableRegister(tokens, miscInst)
miscInst.setTo(toRegister)
val value = requireIntegerToken(tokens, miscInst, 15)
miscInst.setValue(value)
} else if (type == InstructionType.MCP) {
val fromRegister = requireWriteableRegister(tokens, miscInst)
miscInst.setFrom(fromRegister)
val toRegister = requireWriteableRegister(tokens, miscInst)
miscInst.setTo(toRegister)
val value = requireIntegerToken(tokens, miscInst, Short.Companion.MAX_VALUE.toInt())
miscInst.setValue(value)
} else if (type == InstructionType.DEBUG) {
val debugString = requireString(tokens, miscInst)
// create a dummy int token representing the offset of the debug string in the debug info
val debugStringIndex = debugStrings!!.size
debugStrings!!.add(debugString.stringValue)
val value = MTMCToken(0, 0, 0, 0, debugStringIndex.toString(), MTMCToken.TokenType.INTEGER)
miscInst.setValue(value)
}
instruction = miscInst
} else if (type.instructionClass == InstructionClass.ALU) {
val aluInst = ALUInstruction(type, labelTokens, instructionToken)
if (aluInst.isBinaryOp) {
val toRegister = requireWriteableRegister(tokens, aluInst)
aluInst.setTo(toRegister)
val fromRegister = requireReadableRegister(tokens, aluInst)
aluInst.setFrom(fromRegister)
} else if (aluInst.isImmediateOp()) {
val immediateOp = requireALUOp(tokens, aluInst)
// TODO - validate is max or lower op
aluInst.setImmediateOp(immediateOp)
val toRegister = requireWriteableRegister(tokens, aluInst)
aluInst.setTo(toRegister)
val value = requireIntegerToken(tokens, aluInst, Short.Companion.MAX_VALUE.toInt())
aluInst.setImmediateValue(value)
} else {
val toRegister = requireWriteableRegister(tokens, aluInst)
aluInst.setTo(toRegister)
}
instruction = aluInst
} else if (type.instructionClass == InstructionClass.STACK) {
val stackInst = StackInstruction(type, labelTokens, instructionToken)
if (type == InstructionType.PUSH) {
val targetRegister = requireReadableRegister(tokens, stackInst)
stackInst.setTarget(targetRegister)
} else if (type == InstructionType.POP) {
val toRegister = requireWriteableRegister(tokens, stackInst)
stackInst.setTarget(toRegister)
} else if (type == InstructionType.SOP) {
val aluOp = requireAluOp(tokens, stackInst)
stackInst.setALUOp(aluOp)
} else if (type == InstructionType.PUSHI) {
val value = requireIntegerToken(tokens, stackInst, Short.Companion.MAX_VALUE.toInt())
stackInst.setValue(value)
}
// if there is a stack register specified, consume it
if (!tokens.isEmpty() && tokens.peekFirst().type == MTMCToken.TokenType.IDENTIFIER) {
val stackReg = requireReadableRegister(tokens, stackInst)
stackInst.setStackRegister(stackReg)
}
instruction = stackInst
} else if (type.instructionClass == InstructionClass.TEST) {
val testInst = TestInstruction(type, labelTokens, instructionToken)
testInst.setFirst(requireReadableRegister(tokens, testInst))
if (testInst.isImmediate) {
testInst.setImmediateValue(requireIntegerToken(tokens, testInst, 15))
} else {
testInst.setSecond(requireReadableRegister(tokens, testInst))
}
instruction = testInst
} else if (type.instructionClass == InstructionClass.LOAD_STORE) {
val loadInst = LoadStoreInstruction(type, labelTokens, instructionToken)
val targetReg = requireReadableRegister(tokens, loadInst)
loadInst.setTargetToken(targetReg)
if (loadInst.isOffset) {
val offsetReg = requireReadableRegister(tokens, loadInst)
loadInst.setOffsetToken(offsetReg)
}
val value = requireIntegerOrLabelReferenceToken(tokens, loadInst)
loadInst.setValue(value)
instruction = loadInst
} else if (type.instructionClass == InstructionClass.LOAD_STORE_REGISTER) {
val loadInst = LoadStoreRegisterInstruction(type, labelTokens, instructionToken)
val targetReg = requireWriteableRegister(tokens, loadInst)
loadInst.setTargetToken(targetReg)
val pointerReg = requireWriteableRegister(tokens, loadInst)
loadInst.setPointerToken(pointerReg)
// if there is an offset register specified, consume it
if (!tokens.isEmpty() && tokens.peekFirst().type == MTMCToken.TokenType.IDENTIFIER) {
val offsetReg = requireReadableRegister(tokens, loadInst)
loadInst.setOffsetToken(offsetReg)
}
instruction = loadInst
} else if (type.instructionClass == InstructionClass.JUMP_REGISTER) {
val jumpInst = JumpRegisterInstruction(type, labelTokens, instructionToken)
val register = requireReadableRegister(tokens, jumpInst)
jumpInst.setRegister(register)
instruction = jumpInst
} else if (type.instructionClass == InstructionClass.JUMP) {
val jumpInst = JumpInstruction(type, labelTokens, instructionToken)
val labelValue: MTMCToken? = maybeGetLabelReference(tokens)
val valueToken: MTMCToken?
if (labelValue != null) {
valueToken = labelValue
} else {
valueToken = requireIntegerToken(tokens, jumpInst, MonTanaMiniComputer.MEMORY_SIZE)
}
jumpInst.setAddressToken(valueToken)
instruction = jumpInst
} else {
instruction = ErrorInstruction(
listOf(),
instructionToken,
"Unexpected Token"
)
}
for (labelToken in labelTokens) {
if (hasLabel(labelToken.stringValue)) {
instruction.addError(tokens.poll(), "Label already defined: " + labelToken.stringValue)
} else {
labels.put(labelToken.labelValue(), instruction)
}
}
// add error if any tokens are left on the line
if (!tokens.isEmpty()) {
instruction.addError(tokens.poll(), "Unexpected Token")
}
instructions.add(instruction)
}
//===================================================
// tokenization helper functions
//===================================================
private fun requireSysCall(tokens: LinkedList<MTMCToken>, inst: Instruction): MTMCToken {
val sysCallType = tokens.poll()
if (sysCallType == null) {
inst.addError("Syscall required")
} else if (sysCallType.type != MTMCToken.TokenType.IDENTIFIER) {
inst.addError(sysCallType, "Syscall required")
} else if (!SysCall.isSysCall(sysCallType.stringValue)) {
inst.addError(sysCallType, "Unknown syscall : " + sysCallType.stringValue)
}
return sysCallType!!
}
private fun requireAluOp(tokens: LinkedList<MTMCToken>, inst: Instruction): MTMCToken {
val sysCallType = tokens.poll()
if (sysCallType == null) {
inst.addError("Syscall required")
} else if (sysCallType.type != MTMCToken.TokenType.IDENTIFIER) {
inst.addError(sysCallType, "Syscall required")
} else if (!isALUOp(sysCallType.stringValue)) {
inst.addError(sysCallType, "Unknown alu operation : " + sysCallType.stringValue)
}
return sysCallType!!
}
private fun requireWriteableRegister(
tokens: LinkedList<MTMCToken>,
instruction: Instruction
): MTMCToken {
val nextToken = tokens.poll()
if (nextToken == null) {
instruction.addError("Register required")
} else if (nextToken.type != MTMCToken.TokenType.IDENTIFIER) {
instruction.addError(nextToken, "Invalid Register : " + nextToken.stringValue)
} else if (!isWriteable(nextToken.stringValue)) {
instruction.addError(nextToken, "Register not writeable : " + nextToken.stringValue)
}
return nextToken!!
}
private fun requireReadableRegister(
tokens: LinkedList<MTMCToken>,
instruction: Instruction
): MTMCToken {
val nextToken = tokens.poll()
if (nextToken == null) {
instruction.addError("Register required")
} else if (nextToken.type != MTMCToken.TokenType.IDENTIFIER) {
instruction.addError(nextToken, "Invalid Register : " + nextToken.stringValue)
} else if (!isReadable(nextToken.stringValue)) {
instruction.addError(nextToken, "Register not readable : " + nextToken.stringValue)
}
return nextToken!!
}
private fun requireALUOp(tokens: LinkedList<MTMCToken>, instruction: Instruction): MTMCToken {
val nextToken = tokens.poll()
if (nextToken == null || nextToken.type != MTMCToken.TokenType.IDENTIFIER || !isALUOp(nextToken.stringValue)) {
instruction.addError("ALU operation required")
}
return nextToken!!
}
private fun requireString(tokens: LinkedList<MTMCToken>, instruction: ASMElement): MTMCToken {
val nextToken = tokens.poll()
if (nextToken == null || nextToken.type != MTMCToken.TokenType.STRING) {
instruction.addError("String required")
}
return nextToken!!
}
private fun requireIntegerToken(
tokens: LinkedList<MTMCToken>,
inst: ASMElement,
max: Int
): MTMCToken {
val token = tokens.poll()
if (token == null) {
inst.addError("Integer value required")
} else if (isInteger(token)) {
val integerValue = token.intValue()
if (integerValue < 0 || max < integerValue) {
inst.addError(token, "Integer value out of range: 0-" + max)
}
} else {
inst.addError(token, "Integer value expected")
}
return token!!
}
private fun requireToken(
tokens: LinkedList<MTMCToken>,
type: MTMCToken.TokenType,
inst: ASMElement
): MTMCToken? {
val token = tokens.poll()
if (token == null || token.type != type) {
inst.addError(token, "Token " + type.name + " required")
}
return token
}
private fun requireIntegerOrLabelReferenceToken(
tokens: LinkedList<MTMCToken>,
inst: LoadStoreInstruction
): MTMCToken {
val token = tokens.poll()
if (token == null) {
inst.addError("Integer or label value required")
} else if (!isInteger(token) && token.type != MTMCToken.TokenType.IDENTIFIER) {
inst.addError(token, "Integer or label value expected")
}
return token!!
}
private val tokensForLine: LinkedList<MTMCToken>
get() {
val tokens = LinkedList<MTMCToken>()
if (tokenizer!!.more()) {
val first = tokenizer!!.consume()
tokens.add(first)
while (tokenizer!!.more() &&
first.line == tokenizer!!.currentToken().line
) {
tokens.add(tokenizer!!.consume())
}
}
return tokens
}
fun hasLabel(label: String?): Boolean {
return labels.containsKey(label)
}
fun resolveLabel(label: String?): Int {
return labels.get(label)!!.location
}
internal enum class ASMMode {
DATA,
TEXT
}
companion object {
fun transformSyntheticInstructions(tokens: LinkedList<MTMCToken>): LinkedList<MTMCToken> {
if (!tokens.isEmpty()) {
val first = tokens.peekFirst()
if (first.type == MTMCToken.TokenType.IDENTIFIER) {
val stringVal = first.stringValue
if (stringVal.endsWith("i")) {
val op = stringVal.substring(0, stringVal.length - 1)
if (isALUOp(op)) {
val syntheticImmediate = tokens.removeFirst()
tokens.addFirst(syntheticImmediate.cloneWithVal(op))
tokens.addFirst(syntheticImmediate.cloneWithVal("imm"))
}
} else if (stringVal.startsWith("s")) {
val op = stringVal.substring(1, stringVal.length)
if (isALUOp(op)) {
val syntheticImmediate = tokens.removeFirst()
tokens.addFirst(syntheticImmediate.cloneWithVal(op))
tokens.addFirst(syntheticImmediate.cloneWithVal("sop"))
}
} else if (stringVal == "la") {
val syntheticImmediate = tokens.removeFirst()
tokens.addFirst(syntheticImmediate.cloneWithVal("li"))
} else if (stringVal == "ret") {
val syntheticImmediate = tokens.removeFirst()
tokens.addFirst(syntheticImmediate.cloneWithVal("ra"))
tokens.addFirst(syntheticImmediate.cloneWithVal("jr"))
}
}
}
return tokens
}
private fun maybeGetLabels(tokens: LinkedList<MTMCToken>): MutableList<MTMCToken> {
val labels = LinkedList<MTMCToken>()
while (!tokens.isEmpty() && tokens.getFirst().type == MTMCToken.TokenType.LABEL) {
val label = tokens.poll()
labels.add(label!!)
}
return labels
}
private fun maybeGetLabelReference(tokens: LinkedList<MTMCToken>): MTMCToken? {
var label: MTMCToken? = null
if (tokens.getFirst().type == MTMCToken.TokenType.IDENTIFIER) {
label = tokens.poll()
}
return label
}
private fun isInteger(token: MTMCToken?): Boolean {
return token != null && (token.type == MTMCToken.TokenType.INTEGER || token.type == MTMCToken.TokenType.HEX || token.type == MTMCToken.TokenType.BINARY)
}
}
}

View File

@@ -1,20 +0,0 @@
package mtmc.asm
import mtmc.emulator.DebugInfo
@JvmRecord
data class AssemblyResult(
@JvmField val code: ByteArray,
val data: ByteArray,
val graphics: Array<ByteArray>,
@JvmField val debugInfo: DebugInfo?,
@JvmField val errors: MutableList<ASMError>
) {
fun printErrors(): String {
val builder = StringBuilder("Errors:\n")
for (error in errors) {
builder.append(" Line " + error.token.line + ": " + error.error).append('\n')
}
return builder.toString()
}
}

View File

@@ -1,6 +0,0 @@
package mtmc.asm
interface HasLocation {
var location: Int
var sizeInBytes: Int
}

View File

@@ -1,37 +0,0 @@
package mtmc.asm.data
import mtmc.asm.ASMElement
import mtmc.asm.Assembler
import mtmc.tokenizer.MTMCToken
class Data(labels: MutableList<MTMCToken>, lineNumber: Int) : ASMElement(labels, lineNumber) {
var valueToken: MTMCToken? = null
private var value: ByteArray = ByteArray(0)
override var sizeInBytes: Int
get() = value.size
set(value) {}
fun genData(dataBytes: ByteArray, assembler: Assembler) {
val offset = location - assembler.instructionsSizeInBytes
for (i in 0..<sizeInBytes) {
val dataByte = value[i]
dataBytes[offset + i] = dataByte
}
}
fun setValue(src: MTMCToken, value: ByteArray) {
this.valueToken = src
this.value = value
}
override fun toString(): String {
return "Data{" +
"value=" + value.contentToString() +
'}'
}
override fun addError(err: String) {
addError(labels.last(), err)
}
}

View File

@@ -1,51 +0,0 @@
package mtmc.asm.graphics
import mtmc.asm.ASMElement
import mtmc.emulator.MTMCDisplay.Companion.convertImage
import mtmc.tokenizer.MTMCToken
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
import javax.imageio.ImageIO
/**
*
* @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

@@ -1,89 +0,0 @@
package mtmc.asm.instructions
import mtmc.asm.Assembler
import mtmc.emulator.Register.Companion.fromInteger
import mtmc.emulator.Register.Companion.toInteger
import mtmc.tokenizer.MTMCToken
import mtmc.util.BinaryUtils
import java.util.Locale
class ALUInstruction(
type: InstructionType,
label: List<MTMCToken>,
instructionToken: MTMCToken
) : Instruction(type, label, instructionToken) {
private var toToken: MTMCToken? = null
private var fromToken: MTMCToken? = null
private var immediateOp: MTMCToken? = null
private var value: MTMCToken? = null
fun setTo(to: MTMCToken) {
this.toToken = to
}
fun setFrom(from: MTMCToken?) {
this.fromToken = from
}
fun setImmediateValue(value: MTMCToken) {
this.value = value
}
fun setImmediateOp(immediateOp: MTMCToken) {
this.immediateOp = immediateOp
}
fun isImmediateOp(): Boolean {
return this.type == InstructionType.IMM
}
val isBinaryOp: Boolean
get() = !ALUOp.valueOf(instructionToken.stringValue.uppercase(Locale.getDefault())).isUnary
override fun genCode(output: ByteArray, assembler: Assembler) {
val opCode = ALUOp.toInteger(instructionToken.stringValue)
val to = toInteger(toToken!!.stringValue)
var from = 0
if (fromToken != null) {
from = toInteger(fromToken!!.stringValue)
}
output[location] = (16 or opCode).toByte()
if (this.isBinaryOp) {
output[location + 1] = (to shl 4 or from).toByte()
} else if (isImmediateOp()) {
val immediateValue = value!!.intValue()
val immediateOpValue = ALUOp.toInteger(immediateOp!!.stringValue)
output[location + 1] = (to shl 4 or immediateOpValue).toByte()
output[location + 2] = (immediateValue ushr 8).toByte()
output[location + 3] = immediateValue.toByte()
} else { // unary op
output[location + 1] = (to shl 4).toByte()
}
}
companion object {
@JvmStatic
fun disassemble(instruction: Short): String? {
if (BinaryUtils.getBits(16, 4, instruction).toInt() == 1) {
val builder = StringBuilder()
val opCode = BinaryUtils.getBits(12, 4, instruction)
val op = ALUOp.fromInt(opCode)
val aluOp = ALUOp.valueOf(op.uppercase(Locale.getDefault()))
if (aluOp == ALUOp.IMM) {
builder.append(op).append(" ")
builder.append(fromInteger(BinaryUtils.getBits(8, 4, instruction).toInt())).append(" ")
builder.append(ALUOp.fromInt(BinaryUtils.getBits(4, 4, instruction))).append(" ")
} else if (aluOp.isUnary) {
builder.append(op).append(" ")
builder.append(fromInteger(BinaryUtils.getBits(8, 4, instruction).toInt())).append(" ")
} else {
builder.append(op).append(" ")
builder.append(fromInteger(BinaryUtils.getBits(8, 4, instruction).toInt())).append(" ")
builder.append(fromInteger(BinaryUtils.getBits(4, 4, instruction).toInt())).append(" ")
}
return builder.toString()
}
return null
}
}
}

View File

@@ -1,44 +0,0 @@
package mtmc.asm.instructions
import java.util.*
enum class ALUOp(val opCode: Int, val isUnary: Boolean) {
ADD(0x0000, false),
SUB(0x0001, false),
MUL(0x0002, false),
DIV(0x0003, false),
MOD(0x0004, false),
AND(0x0005, false),
OR(0x0006, false),
XOR(0x0007, false),
SHL(0x0008, false),
SHR(0x0009, false),
MIN(0x000A, false),
MAX(0x000B, false),
NOT(0x000C, true),
LNOT(0x000D, true),
NEG(0x000E, true),
IMM(0x000F, true);
companion object {
@JvmStatic
fun toInteger(instruction: String): Int {
return valueOf(instruction.uppercase(Locale.getDefault())).opCode
}
@JvmStatic
fun fromInt(opCode: Short): String {
return entries[opCode.toInt()].name.lowercase(Locale.getDefault())
}
@JvmStatic
fun isALUOp(op: String): Boolean {
try {
val aluOp = valueOf(op.uppercase(Locale.getDefault()))
return true
} catch (e: IllegalArgumentException) {
return false
}
}
}
}

View File

@@ -1,23 +0,0 @@
package mtmc.asm.instructions
import mtmc.asm.Assembler
import mtmc.tokenizer.MTMCToken
class ErrorInstruction(
labels: List<MTMCToken>,
instruction: MTMCToken,
error: String
) : Instruction(
InstructionType.ERROR,
labels,
instruction
) {
init {
addError(instruction, error)
}
override fun genCode(output: ByteArray, assembler: Assembler) {
// do nothing
}
}

View File

@@ -1,72 +0,0 @@
package mtmc.asm.instructions
import mtmc.asm.ASMElement
import mtmc.asm.Assembler
import mtmc.asm.instructions.ALUInstruction.Companion.disassemble
import mtmc.emulator.MonTanaMiniComputer.Companion.isDoubleWordInstruction
import mtmc.tokenizer.MTMCToken
abstract class Instruction(
@JvmField val type: InstructionType,
labels: List<MTMCToken>,
@JvmField val instructionToken: MTMCToken
) : ASMElement(labels, instructionToken.line) {
open fun validateLabel(assembler: Assembler) {
// default does nothing
}
override fun addError(error: String) {
addError(instructionToken, error)
}
abstract fun genCode(output: ByteArray, assembler: Assembler)
override var sizeInBytes: Int = type.sizeInBytes
get() = type.sizeInBytes
companion object {
@JvmStatic
fun isInstruction(cmd: String): Boolean {
return InstructionType.fromString(cmd) != null
}
fun disassemble(instruction: Short, previousInstruction: Short): String {
if (isDoubleWordInstruction(previousInstruction)) {
return instruction.toInt().toString()
}
val misc = MiscInstruction.disassemble(instruction)
if (misc != null) {
return misc
}
val aluOp = disassemble(instruction)
if (aluOp != null) {
return aluOp
}
val stack = StackInstruction.disassemble(instruction)
if (stack != null) {
return stack
}
val test = TestInstruction.disassemble(instruction)
if (test != null) {
return test
}
val lsr = LoadStoreRegisterInstruction.disassemble(instruction)
if (lsr != null) {
return lsr
}
val ls = LoadStoreInstruction.disassemble(instruction)
if (ls != null) {
return ls
}
val jumpReg = JumpInstruction.disassemble(instruction)
if (jumpReg != null) {
return jumpReg
}
val jump = JumpInstruction.disassemble(instruction)
if (jump != null) {
return jump
}
return "<unknown>"
}
}
}

View File

@@ -1,98 +0,0 @@
package mtmc.asm.instructions
import java.util.*
enum class InstructionType @JvmOverloads constructor(
@JvmField val instructionClass: InstructionClass?,
val sizeInBytes: Int = 2
) {
SYS(InstructionClass.MISC),
MOV(InstructionClass.MISC),
INC(InstructionClass.MISC),
DEC(InstructionClass.MISC),
SETI(InstructionClass.MISC),
NOP(InstructionClass.MISC),
MCP(InstructionClass.MISC, 4),
DEBUG(InstructionClass.MISC),
ADD(InstructionClass.ALU),
SUB(InstructionClass.ALU),
MUL(InstructionClass.ALU),
DIV(InstructionClass.ALU),
MOD(InstructionClass.ALU),
AND(InstructionClass.ALU),
OR(InstructionClass.ALU),
XOR(InstructionClass.ALU),
SHL(InstructionClass.ALU),
SHR(InstructionClass.ALU),
MIN(InstructionClass.ALU),
MAX(InstructionClass.ALU),
NOT(InstructionClass.ALU),
LNOT(InstructionClass.ALU),
NEG(InstructionClass.ALU),
IMM(InstructionClass.ALU, 4),
PUSH(InstructionClass.STACK),
POP(InstructionClass.STACK),
DUP(InstructionClass.STACK),
SWAP(InstructionClass.STACK),
DROP(InstructionClass.STACK),
OVER(InstructionClass.STACK),
ROT(InstructionClass.STACK),
SOP(InstructionClass.STACK),
PUSHI(InstructionClass.STACK, 4),
EQ(InstructionClass.TEST),
NEQ(InstructionClass.TEST),
GT(InstructionClass.TEST),
GTE(InstructionClass.TEST),
LT(InstructionClass.TEST),
LTE(InstructionClass.TEST),
EQI(InstructionClass.TEST),
NEQI(InstructionClass.TEST),
GTI(InstructionClass.TEST),
GTEI(InstructionClass.TEST),
LTI(InstructionClass.TEST),
LTEI(InstructionClass.TEST),
LWR(InstructionClass.LOAD_STORE_REGISTER),
LBR(InstructionClass.LOAD_STORE_REGISTER),
SWR(InstructionClass.LOAD_STORE_REGISTER),
SBR(InstructionClass.LOAD_STORE_REGISTER),
LW(InstructionClass.LOAD_STORE, 4),
LWO(InstructionClass.LOAD_STORE, 4),
LI(InstructionClass.LOAD_STORE, 4),
LB(InstructionClass.LOAD_STORE, 4),
LBO(InstructionClass.LOAD_STORE, 4),
SW(InstructionClass.LOAD_STORE, 4),
SWO(InstructionClass.LOAD_STORE, 4),
SB(InstructionClass.LOAD_STORE, 4),
SBO(InstructionClass.LOAD_STORE, 4),
JR(InstructionClass.JUMP_REGISTER),
J(InstructionClass.JUMP),
JZ(InstructionClass.JUMP),
JNZ(InstructionClass.JUMP),
JAL(InstructionClass.JUMP),
META(InstructionClass.MISC),
ERROR(InstructionClass.MISC),
;
enum class InstructionClass {
MISC,
ALU,
STACK,
TEST,
LOAD_STORE_REGISTER,
LOAD_STORE,
JUMP_REGISTER,
JUMP
}
companion object {
@JvmStatic
fun fromString(string: String): InstructionType? {
try {
return valueOf(string.uppercase(Locale.getDefault()))
} catch (e: IllegalArgumentException) {
return null
}
}
}
}

View File

@@ -1,69 +0,0 @@
package mtmc.asm.instructions
import mtmc.asm.Assembler
import mtmc.tokenizer.MTMCToken
import mtmc.util.BinaryUtils
class JumpInstruction(
type: InstructionType,
label: List<MTMCToken>,
instructionToken: MTMCToken
) : Instruction(type, label, instructionToken) {
private var addressToken: MTMCToken? = null
fun setAddressToken(addressToken: MTMCToken) {
this.addressToken = addressToken
}
override fun validateLabel(assembler: Assembler) {
if (addressToken!!.type == MTMCToken.TokenType.IDENTIFIER) {
if (!assembler.hasLabel(addressToken!!.stringValue)) {
addError("Unresolved label: " + addressToken!!.stringValue)
}
}
}
private fun resolveTargetAddress(assembler: Assembler): Int? {
if (addressToken!!.type == MTMCToken.TokenType.IDENTIFIER) {
return assembler.resolveLabel(addressToken!!.stringValue)
} else {
return addressToken!!.intValue()
}
}
override fun genCode(output: ByteArray, assembler: Assembler) {
var opcode = 0
when (type) {
InstructionType.J -> opcode = 12
InstructionType.JZ -> opcode = 13
InstructionType.JNZ -> opcode = 14
InstructionType.JAL -> opcode = 15
else -> println("Invalid instruction type")
}
val address = resolveTargetAddress(assembler)!!
output[location] = (opcode shl 4 or (address ushr 8)).toByte()
output[location + 1] = address.toByte()
}
companion object {
fun disassemble(instruction: Short): String? {
if (BinaryUtils.getBits(16, 2, instruction).toInt() == 3) {
val jumpType = BinaryUtils.getBits(14, 2, instruction)
val sb = StringBuilder()
if (jumpType.toInt() == 0) {
sb.append("j")
} else if (jumpType.toInt() == 1) {
sb.append("jz")
} else if (jumpType.toInt() == 2) {
sb.append("jnz")
} else if (jumpType.toInt() == 3) {
sb.append("jal")
}
val target = BinaryUtils.getBits(12, 12, instruction)
sb.append(" ").append(target.toInt())
return sb.toString()
}
return null
}
}
}

View File

@@ -1,37 +0,0 @@
package mtmc.asm.instructions
import mtmc.asm.Assembler
import mtmc.emulator.Register.Companion.fromInteger
import mtmc.emulator.Register.Companion.toInteger
import mtmc.tokenizer.MTMCToken
import mtmc.util.BinaryUtils
class JumpRegisterInstruction(
type: InstructionType,
label: List<MTMCToken>,
instructionToken: MTMCToken
) : Instruction(type, label, instructionToken) {
private var register: MTMCToken? = null
fun setRegister(register: MTMCToken) {
this.register = register
}
public override fun genCode(output: ByteArray, assembler: Assembler) {
val opcode = 9
output[location] = (opcode shl 4).toByte()
val reg = toInteger(register!!.stringValue)
output[location + 1] = reg.toByte()
}
companion object {
fun disassemble(instruction: Short): String? {
if (BinaryUtils.getBits(16, 5, instruction).toInt() == 9) {
val reg = BinaryUtils.getBits(4, 4, instruction)
val sb = StringBuilder("jr")
sb.append(fromInteger(reg.toInt()))
}
return null
}
}
}

View File

@@ -1,115 +0,0 @@
package mtmc.asm.instructions
import mtmc.asm.Assembler
import mtmc.emulator.Register.Companion.fromInteger
import mtmc.emulator.Register.Companion.toInteger
import mtmc.tokenizer.MTMCToken
import mtmc.util.BinaryUtils
class LoadStoreInstruction(
type: InstructionType,
label: List<MTMCToken>,
instructionToken: MTMCToken
) : Instruction(type, label, instructionToken) {
private var targetToken: MTMCToken? = null
private var offsetToken: MTMCToken? = null
private var value: MTMCToken? = null
fun setTargetToken(targetToken: MTMCToken) {
this.targetToken = targetToken
}
fun setOffsetToken(offsetToken: MTMCToken) {
this.offsetToken = offsetToken
}
fun setValue(value: MTMCToken) {
this.value = value
}
val isOffset: Boolean
get() = type.name.endsWith("O")
public override fun validateLabel(assembler: Assembler) {
if (value!!.type == MTMCToken.TokenType.IDENTIFIER) {
if (!assembler.hasLabel(value!!.stringValue)) {
addError("Unresolved label: " + value!!.stringValue)
}
}
}
private fun resolveValue(assembler: Assembler): Int? {
if (value!!.type == MTMCToken.TokenType.IDENTIFIER) {
return assembler.resolveLabel(value!!.stringValue)
} else {
return value!!.intValue()
}
}
override fun genCode(output: ByteArray, assembler: Assembler) {
val upperByte = when (type) {
InstructionType.LW -> 128
InstructionType.LWO -> 129
InstructionType.LB -> 130
InstructionType.LBO -> 131
InstructionType.SW -> 132
InstructionType.SWO -> 133
InstructionType.SB -> 134
InstructionType.SBO -> 135
InstructionType.LI -> 143
else -> 0
}
val target = toInteger(targetToken!!.stringValue)
output[location] = upperByte.toByte()
if (this.isOffset) {
val offsetReg = toInteger(offsetToken!!.stringValue)
output[location + 1] = (target shl 4 or offsetReg).toByte()
} else {
output[location + 1] = (target shl 4).toByte()
}
val numericValue = resolveValue(assembler)!!
output[location + 2] = (numericValue ushr 8).toByte()
output[location + 3] = numericValue.toByte()
}
companion object {
fun disassemble(instruction: Short): String? {
if (BinaryUtils.getBits(16, 4, instruction).toInt() == 8) {
val topNibble = BinaryUtils.getBits(12, 4, instruction)
val sb = StringBuilder()
if (topNibble.toInt() == 15) {
sb.append("li ")
} else if (topNibble.toInt() == 0) {
sb.append("lw ")
} else if (topNibble.toInt() == 1) {
sb.append("lwo ")
} else if (topNibble.toInt() == 2) {
sb.append("lb ")
} else if (topNibble.toInt() == 3) {
sb.append("lbo ")
} else if (topNibble.toInt() == 4) {
sb.append("sw ")
} else if (topNibble.toInt() == 5) {
sb.append("swo ")
} else if (topNibble.toInt() == 6) {
sb.append("sb ")
} else if (topNibble.toInt() == 7) {
sb.append("sbo ")
}
val target = BinaryUtils.getBits(8, 4, instruction)
val reg = fromInteger(target.toInt())
sb.append(reg)
if (topNibble.toInt() == 1 || topNibble.toInt() == 3 || topNibble.toInt() == 5 || topNibble.toInt() == 7) {
val offset = BinaryUtils.getBits(4, 4, instruction)
val offsetReg = fromInteger(offset.toInt())
sb.append(" ").append(offsetReg)
}
return sb.toString()
}
return null
}
}
}

View File

@@ -1,75 +0,0 @@
package mtmc.asm.instructions
import mtmc.asm.Assembler
import mtmc.emulator.Register
import mtmc.emulator.Register.Companion.fromInteger
import mtmc.emulator.Register.Companion.toInteger
import mtmc.tokenizer.MTMCToken
import mtmc.util.BinaryUtils
class LoadStoreRegisterInstruction(
type: InstructionType,
label: List<MTMCToken>,
instructionToken: MTMCToken
) : Instruction(type, label, instructionToken) {
private var targetToken: MTMCToken? = null
private var pointerToken: MTMCToken? = null
private var offsetToken: MTMCToken? = null
fun setTargetToken(targetToken: MTMCToken) {
this.targetToken = targetToken
}
fun setPointerToken(pointerToken: MTMCToken) {
this.pointerToken = pointerToken
}
fun setOffsetToken(offsetToken: MTMCToken?) {
this.offsetToken = offsetToken
}
override fun genCode(output: ByteArray, assembler: Assembler) {
var opcode = 0
when (type) {
InstructionType.LWR -> opcode = 4
InstructionType.LBR -> opcode = 5
InstructionType.SWR -> opcode = 6
InstructionType.SBR -> opcode = 7
else -> println("Invalid instruction type")
}
val target = toInteger(targetToken!!.stringValue)
val pointer = toInteger(pointerToken!!.stringValue)
var offset = Register.PC.ordinal
if (offsetToken != null) {
offset = toInteger(offsetToken!!.stringValue)
}
output[location] = (opcode shl 4 or target).toByte()
output[location + 1] = (pointer shl 4 or offset).toByte()
}
companion object {
fun disassemble(instruction: Short): String? {
if (BinaryUtils.getBits(16, 2, instruction).toInt() == 1) {
val builder = StringBuilder()
val type = BinaryUtils.getBits(14, 2, instruction)
if (type.toInt() == 0) {
builder.append("lwr ")
} else if (type.toInt() == 1) {
builder.append("lbr ")
} else if (type.toInt() == 2) {
builder.append("swr ")
} else if (type.toInt() == 3) {
builder.append("sbr ")
}
val srcDestReg = BinaryUtils.getBits(12, 4, instruction)
val addrReg = BinaryUtils.getBits(8, 4, instruction)
val offsetReg = BinaryUtils.getBits(4, 4, instruction)
builder.append(fromInteger(srcDestReg.toInt())).append(" ")
builder.append(fromInteger(addrReg.toInt())).append(" ")
builder.append(fromInteger(offsetReg.toInt()))
return builder.toString()
}
return null
}
}
}

View File

@@ -1,90 +0,0 @@
package mtmc.asm.instructions
import mtmc.asm.Assembler
import mtmc.tokenizer.MTMCToken
class MetaInstruction(instruction: MTMCToken) :
Instruction(InstructionType.META, listOf(), instruction) {
private var originalFilePath: MTMCToken? = null
private var originaLineNumber: MTMCToken? = null
private var globalName: MTMCToken? = null
private var globalLocation: MTMCToken? = null
private var globalType: MTMCToken? = null
private var localName: MTMCToken? = null
private var localOffset: MTMCToken? = null
private var localType: MTMCToken? = null
override fun genCode(output: ByteArray, assembler: Assembler) {
// do nothing
}
val isFileDirective: Boolean
get() = "file" == this.instructionToken.stringValue
val isLineDirective: Boolean
get() = "line" == this.instructionToken.stringValue
val isGlobalDirective: Boolean
get() = "global" == this.instructionToken.stringValue
val isLocalDirective: Boolean
get() = "local" == this.instructionToken.stringValue
val isEndLocalDirective: Boolean
get() = "endlocal" == this.instructionToken.stringValue
fun setOriginalFilePath(path: MTMCToken) {
this.originalFilePath = path
}
fun setOriginalLineNumber(lineNumber: MTMCToken) {
this.originaLineNumber = lineNumber
}
val originalLineNumber: Int
get() = this.originaLineNumber!!.intValue()
fun setGlobalInfo(name: MTMCToken, location: MTMCToken, type: MTMCToken) {
this.globalName = name
this.globalLocation = location
this.globalType = type
}
fun setLocalInfo(name: MTMCToken, offset: MTMCToken, type: MTMCToken) {
this.localName = name
this.localOffset = offset
this.localType = type
}
fun setEndLocalInfo(name: MTMCToken) {
this.localName = name
}
fun getOriginalFilePath(): String? {
return this.originalFilePath!!.stringValue
}
fun getGlobalName(): String? {
return this.globalName!!.stringValue
}
fun getGlobalLocation(): Int {
return this.globalLocation!!.intValue()
}
fun getGlobalType(): String? {
return this.globalType!!.stringValue
}
fun getLocalName(): String? {
return this.localName!!.stringValue
}
fun getLocalOffset(): Int {
return this.localOffset!!.intValue()
}
fun getLocalType(): String? {
return this.localType!!.stringValue
}
}

View File

@@ -1,145 +0,0 @@
package mtmc.asm.instructions
import mtmc.asm.Assembler
import mtmc.emulator.Register.Companion.fromInteger
import mtmc.emulator.Register.Companion.toInteger
import mtmc.os.SysCall
import mtmc.tokenizer.MTMCToken
import mtmc.util.BinaryUtils
class MiscInstruction(
type: InstructionType,
labels: List<MTMCToken>,
instructionToken: MTMCToken
) : Instruction(type, labels, instructionToken) {
private var syscallType: MTMCToken? = null
private var fromRegister: MTMCToken? = null
private var toRegister: MTMCToken? = null
private var value: MTMCToken? = null
fun setSyscallType(type: MTMCToken) {
this.syscallType = type
}
fun setFrom(fromRegister: MTMCToken) {
this.fromRegister = fromRegister
}
fun setTo(toRegister: MTMCToken) {
this.toRegister = toRegister
}
fun setValue(value: MTMCToken?) {
this.value = value
}
override fun genCode(output: ByteArray, assembler: Assembler) {
if (type == InstructionType.SYS) {
output[location] = 0
output[location + 1] = SysCall.getValue(this.syscallType!!.stringValue)
} else if (type == InstructionType.MOV) {
val to = toInteger(toRegister!!.stringValue)
val from = toInteger(fromRegister!!.stringValue)
output[location] = 1
output[location + 1] = (to shl 4 or from).toByte()
} else if (type == InstructionType.INC) {
output[location] = 2
val to = toInteger(toRegister!!.stringValue)
var immediateVal = 1
if (value != null) {
immediateVal = value!!.intValue()
}
output[location + 1] = (to shl 4 or immediateVal).toByte()
} else if (type == InstructionType.DEC) {
output[location] = 3
val to = toInteger(toRegister!!.stringValue)
var immediateVal = 1
if (value != null) {
immediateVal = value!!.intValue()
}
output[location + 1] = (to shl 4 or immediateVal).toByte()
} else if (type == InstructionType.SETI) {
output[location] = 4
val to = toInteger(toRegister!!.stringValue)
val immediateVal = value!!.intValue()
output[location + 1] = (to shl 4 or immediateVal).toByte()
} else if (type == InstructionType.MCP) {
output[location] = 5
val from = toInteger(fromRegister!!.stringValue)
val to = toInteger(toRegister!!.stringValue)
val value = this.value!!.intValue()
output[location + 1] = (from shl 4 or to).toByte()
output[location + 2] = (value shl 8).toByte()
output[location + 3] = (value and 0xFF).toByte()
} else if (type == InstructionType.DEBUG) {
output[location] = 8
output[location + 1] = value!!.intValue().toByte()
} else if (type == InstructionType.NOP) {
output[location] = 15
output[location + 1] = 255.toByte()
}
}
companion object {
fun disassemble(instruction: Short): String? {
if (BinaryUtils.getBits(16, 4, instruction).toInt() == 0) {
val topNibble = BinaryUtils.getBits(12, 4, instruction)
if (topNibble.toInt() == 0) {
val builder = StringBuilder("sys ")
val bits = BinaryUtils.getBits(8, 8, instruction)
val name = SysCall.getString(bits.toByte())
builder.append(name)
return builder.toString()
} else if (topNibble.toInt() == 1) {
val builder = StringBuilder("mov ")
val to = BinaryUtils.getBits(8, 4, instruction)
val from = BinaryUtils.getBits(4, 4, instruction)
val toName = fromInteger(to.toInt())
builder.append(toName).append(" ")
val fromName = fromInteger(from.toInt())
builder.append(fromName)
return builder.toString()
} else if (topNibble.toInt() == 2) {
val builder = StringBuilder("inc ")
val to = BinaryUtils.getBits(8, 4, instruction)
val amount = BinaryUtils.getBits(4, 4, instruction)
val toName = fromInteger(to.toInt())
builder.append(toName).append(" ")
builder.append(amount.toInt())
return builder.toString()
} else if (topNibble.toInt() == 3) {
val builder = StringBuilder("dec ")
val to = BinaryUtils.getBits(8, 4, instruction)
val amount = BinaryUtils.getBits(4, 4, instruction)
val toName = fromInteger(to.toInt())
builder.append(toName).append(" ")
builder.append(amount.toInt())
return builder.toString()
} else if (topNibble.toInt() == 4) {
val builder = StringBuilder("seti ")
val to = BinaryUtils.getBits(8, 4, instruction)
val amount = BinaryUtils.getBits(4, 4, instruction)
val toName = fromInteger(to.toInt())
builder.append(toName).append(" ")
builder.append(amount.toInt())
return builder.toString()
} else if (topNibble.toInt() == 5) {
val from = BinaryUtils.getBits(8, 4, instruction)
val fromName = fromInteger(from.toInt())
val to = BinaryUtils.getBits(4, 4, instruction)
val toName = fromInteger(to.toInt())
return "mcp " + fromName + " " + toName
} else if (topNibble.toInt() == 8) {
val builder = StringBuilder("debug ")
val stringIndex = BinaryUtils.getBits(8, 8, instruction)
builder.append(stringIndex.toInt())
return builder.toString()
} else if (topNibble.toInt() == 15) {
val builder = StringBuilder("noop")
return builder.toString()
}
}
return null
}
}
}

View File

@@ -1,108 +0,0 @@
package mtmc.asm.instructions
import mtmc.asm.Assembler
import mtmc.asm.instructions.ALUOp.Companion.fromInt
import mtmc.emulator.Register
import mtmc.emulator.Register.Companion.fromInteger
import mtmc.tokenizer.MTMCToken
import mtmc.util.BinaryUtils
class StackInstruction(
type: InstructionType,
label: List<MTMCToken>,
instructionToken: MTMCToken
) : Instruction(type, label, instructionToken) {
private var targetToken: MTMCToken? = null
private var stackRegisterToken: MTMCToken? = null
private var aluOpToken: MTMCToken? = null
private var value: MTMCToken? = null
fun setTarget(target: MTMCToken) {
this.targetToken = target
}
fun setStackRegister(stackRegister: MTMCToken?) {
this.stackRegisterToken = stackRegister
}
fun setALUOp(aluOp: MTMCToken) {
this.aluOpToken = aluOp
}
fun setValue(value: MTMCToken) {
this.value = value
}
override fun genCode(output: ByteArray, assembler: Assembler) {
var stackReg = Register.SP.ordinal
if (stackRegisterToken != null) {
stackReg = Register.Companion.toInteger(stackRegisterToken!!.stringValue)
}
if (type == InstructionType.PUSH) {
val target = Register.Companion.toInteger(targetToken!!.stringValue)
output[location] = 32
output[location + 1] = (target shl 4 or stackReg).toByte()
} else if (type == InstructionType.POP) {
val target = Register.Companion.toInteger(targetToken!!.stringValue)
output[location] = 33
output[location + 1] = (target shl 4 or stackReg).toByte()
} else if (type == InstructionType.SOP) {
val aluOp = ALUOp.toInteger(aluOpToken!!.stringValue)
output[location] = 39
output[location + 1] = (aluOp shl 4 or stackReg).toByte()
} else if (type == InstructionType.PUSHI) {
val immediateValue = value!!.intValue()
output[location] = 47
output[location + 1] = stackReg.toByte()
output[location + 2] = (immediateValue ushr 8).toByte()
output[location + 3] = immediateValue.toByte()
} else {
val stackOp: Int
stackOp = when (type) {
InstructionType.DUP -> 34
InstructionType.SWAP -> 35
InstructionType.DROP -> 36
InstructionType.OVER -> 37
InstructionType.ROT -> 38
else -> 0
}
output[location] = stackOp.toByte()
output[location + 1] = stackReg.toByte()
}
}
companion object {
fun disassemble(instruction: Short): String? {
if (BinaryUtils.getBits(16, 4, instruction).toInt() == 2) {
val opCode = BinaryUtils.getBits(12, 4, instruction)
val stackReg = BinaryUtils.getBits(4, 4, instruction)
if (opCode.toInt() == 0) {
val sourceReg = BinaryUtils.getBits(8, 4, instruction)
return "push " + fromInteger(sourceReg.toInt()) + " " + fromInteger(stackReg.toInt())
}
if (opCode.toInt() == 1) {
val destReg = BinaryUtils.getBits(8, 4, instruction)
return "pop " + fromInteger(destReg.toInt()) + " " + fromInteger(stackReg.toInt())
}
if (opCode.toInt() == 2) {
return "dup " + fromInteger(stackReg.toInt())
} else if (opCode.toInt() == 3) {
return "swap " + fromInteger(stackReg.toInt())
} else if (opCode.toInt() == 4) {
return "drop " + fromInteger(stackReg.toInt())
} else if (opCode.toInt() == 5) {
return "over " + fromInteger(stackReg.toInt())
} else if (opCode.toInt() == 6) {
return "rot " + fromInteger(stackReg.toInt())
} else if (opCode.toInt() == 7) {
val aluOp = BinaryUtils.getBits(8, 4, instruction)
val op = fromInt(aluOp)
return "sop " + op + " " + fromInteger(stackReg.toInt())
} else if (opCode.toInt() == 15) {
return "pushi " + fromInteger(stackReg.toInt())
}
}
return null
}
}
}

View File

@@ -1,126 +0,0 @@
package mtmc.asm.instructions
import mtmc.asm.Assembler
import mtmc.emulator.Register.Companion.fromInteger
import mtmc.emulator.Register.Companion.toInteger
import mtmc.tokenizer.MTMCToken
import mtmc.util.BinaryUtils
class TestInstruction(
type: InstructionType,
label: List<MTMCToken>,
instructionToken: MTMCToken
) : Instruction(type, label, instructionToken) {
private var first: MTMCToken? = null
private var second: MTMCToken? = null
private var value: MTMCToken? = null
fun setFirst(to: MTMCToken) {
this.first = to
}
fun setSecond(from: MTMCToken) {
this.second = from
}
fun setImmediateValue(value: MTMCToken) {
this.value = value
}
val isImmediate: Boolean
get() = type.name.endsWith("I")
override fun genCode(output: ByteArray, assembler: Assembler) {
val firstReg = toInteger(first!!.stringValue)
if (this.isImmediate) {
val upperByte = when (type) {
InstructionType.EQI -> 56
InstructionType.NEQI -> 57
InstructionType.GTI -> 58
InstructionType.GTEI -> 59
InstructionType.LTI -> 60
InstructionType.LTEI -> 61
else -> 0
}
val immediateValue = value!!.intValue()
output[location] = upperByte.toByte()
output[location + 1] = (firstReg shl 4 or immediateValue).toByte()
} else {
val upperByte = when (type) {
InstructionType.EQ -> 48
InstructionType.NEQ -> 49
InstructionType.GT -> 50
InstructionType.GTE -> 51
InstructionType.LT -> 52
InstructionType.LTE -> 53
else -> 0
}
val secondReg = toInteger(second!!.stringValue)
output[location] = upperByte.toByte()
output[location + 1] = (firstReg shl 4 or secondReg).toByte()
}
}
companion object {
fun disassemble(instruction: Short): String? {
if (BinaryUtils.getBits(16, 4, instruction).toInt() == 3) {
val sb = StringBuilder()
val opCode = BinaryUtils.getBits(12, 4, instruction)
val thirdNibble = BinaryUtils.getBits(8, 4, instruction)
val fourthNibble = BinaryUtils.getBits(4, 4, instruction)
if (opCode.toInt() == 0) {
sb.append("eq ")
sb.append(fromInteger(thirdNibble.toInt()))
.append(" ").append(fromInteger(fourthNibble.toInt()))
} else if (opCode.toInt() == 1) {
sb.append("neq ")
sb.append(fromInteger(thirdNibble.toInt()))
.append(" ").append(fromInteger(fourthNibble.toInt()))
} else if (opCode.toInt() == 2) {
sb.append("gt ")
sb.append(fromInteger(thirdNibble.toInt()))
.append(" ").append(fromInteger(fourthNibble.toInt()))
} else if (opCode.toInt() == 3) {
sb.append("gte ")
sb.append(fromInteger(thirdNibble.toInt()))
.append(" ").append(fromInteger(fourthNibble.toInt()))
} else if (opCode.toInt() == 4) {
sb.append("lt ")
sb.append(fromInteger(thirdNibble.toInt()))
.append(" ").append(fromInteger(fourthNibble.toInt()))
} else if (opCode.toInt() == 5) {
sb.append("lte ")
sb.append(fromInteger(thirdNibble.toInt()))
.append(" ").append(fromInteger(fourthNibble.toInt()))
}
if (opCode.toInt() == 8) {
sb.append("eqi ")
sb.append(fromInteger(thirdNibble.toInt()))
.append(" ").append(fourthNibble.toInt())
} else if (opCode.toInt() == 9) {
sb.append("neqi ")
sb.append(fromInteger(thirdNibble.toInt()))
.append(" ").append(fourthNibble.toInt())
} else if (opCode.toInt() == 10) {
sb.append("gti ")
sb.append(fromInteger(thirdNibble.toInt()))
.append(" ").append(fourthNibble.toInt())
} else if (opCode.toInt() == 11) {
sb.append("gtei ")
sb.append(fromInteger(thirdNibble.toInt()))
.append(" ").append(fourthNibble.toInt())
} else if (opCode.toInt() == 12) {
sb.append("lti ")
sb.append(fromInteger(thirdNibble.toInt()))
.append(" ").append(fourthNibble.toInt())
} else if (opCode.toInt() == 13) {
sb.append("ltei ")
sb.append(fromInteger(thirdNibble.toInt()))
.append(" ").append(fourthNibble.toInt())
}
return sb.toString()
}
return null
}
}
}

View File

@@ -1,45 +0,0 @@
package mtmc.emulator
import java.util.*
import java.util.regex.Pattern
@JvmRecord
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(Locale.getDefault()))
formattedString.append(monTanaMiniComputer.getRegisterValue(register).toInt())
start = matcher.end()
} catch (e: Exception) {
formattedString.append(match)
}
}
formattedString.append(debugString.substring(start))
println("DEBUG[" + System.nanoTime() + "] : " + formattedString)
}
@JvmRecord
data class GlobalInfo(val name: String?, val location: Int, val type: String?)
@JvmRecord
data class LocalInfo(val name: String?, val offset: Int, val type: String?)
}

View File

@@ -1,71 +0,0 @@
package mtmc.emulator
import mtmc.emulator.MonTanaMiniComputer.ComputerStatus
import kotlin.math.max
/**
*
* @author jbanes
*/
class MTMCClock
(private val computer: MonTanaMiniComputer) {
fun run() {
var instructions: Long = 0
var ips: Long = 0
var expected: Long = 0
var virtual: Long = 0
var startTime = System.currentTimeMillis()
var deltaStart: Long
var delta: Long
var speed: Long = 0
var pulse: Long
var ms: Long = 10
while (computer.getStatus() == ComputerStatus.EXECUTING) {
speed = max(computer.speed, 0).toLong()
pulse = (if (speed <= 0) 1000000 else max(speed / 100, 1))
ms = (if (pulse < 10) 1000 / speed else 10)
deltaStart = System.currentTimeMillis()
delta = ms - (System.currentTimeMillis() - deltaStart)
/* We've lost more than a second. Recalibrate. */
if ((expected - virtual) > pulse * 100) {
startTime = deltaStart
virtual = 0
}
/* Throttles to every 10ms, but "catches up" if we're behind */
if (delta > 0 && (expected - virtual) < pulse && speed != 0L) {
try {
Thread.sleep(delta)
} catch (e: InterruptedException) {
}
}
instructions += computer.pulse(pulse)
virtual += pulse
ips = (virtual * 1000) / max(1, System.currentTimeMillis() - startTime)
expected = (System.currentTimeMillis() - startTime) * speed / 1000
}
System.err.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,131 +0,0 @@
package mtmc.emulator
import mtmc.os.shell.Shell
import mtmc.tokenizer.MTMCScanner
import mtmc.tokenizer.MTMCToken
import java.io.Console
class MTMCConsole(private val computer: MonTanaMiniComputer) {
var mode: Mode = Mode.NON_INTERACTIVE
var sysConsole: Console? = null
// non-interactive data
private val output = StringBuffer()
private var shortValueSet = false
private var shortValue: Short = 0
private var stringValue: String? = null
// TODO invert so shell is driving and console is just IO
fun start() {
mode = Mode.INTERACTIVE
sysConsole = System.console()
Shell.printShellWelcome(computer)
while (true) {
val cmd = sysConsole!!.readLine("mtmc > ")
computer.oS.processCommand(cmd)
}
}
fun println(x: String) {
print(x)
print("\n")
}
fun print(x: String) {
output.append(x)
if (mode == Mode.INTERACTIVE) {
kotlin.io.print(x)
} else {
if (x.contains("\n")) {
computer.notifyOfConsoleUpdate()
} else {
computer.notifyOfConsolePrinting()
}
}
}
fun readChar(): Char {
if (mode == Mode.INTERACTIVE) {
val tokens = MTMCScanner(sysConsole!!.readLine(), "#").tokenize()
val token = tokens.first()
assert(token.type === MTMCToken.TokenType.CHAR)
return token.charValue()
} else {
this.shortValueSet = false
return Char(this.shortValue.toUShort())
}
}
fun readInt(): Short {
if (mode == Mode.INTERACTIVE) {
return sysConsole!!.readLine().toShort()
} else {
this.shortValueSet = false
return shortValue
}
}
fun hasShortValue(): Boolean {
return (mode == Mode.INTERACTIVE || shortValueSet)
}
fun setShortValue(shortValue: Short) {
this.shortValue = shortValue
this.shortValueSet = true
}
fun setCharValue(charValue: Char) {
this.shortValue = charValue.code.toShort()
this.shortValueSet = true
}
fun getOutput(): String {
return output.toString()
}
fun consumeLines(): String {
val index = output.lastIndexOf("\n")
val text = if (index >= 0) output.substring(0, index + 1) else ""
if (index >= 0) {
output.delete(0, index + 1)
}
return text
}
fun writeInt(value: Short) {
print(value.toString() + "")
}
fun setStringValue(stringValue: String?) {
this.stringValue = stringValue
}
fun readString(): String? {
if (mode == Mode.INTERACTIVE) {
return sysConsole!!.readLine()
} else {
val stringValue = this.stringValue
this.stringValue = null
return stringValue
}
}
fun hasReadString(): Boolean {
return (mode == Mode.INTERACTIVE || stringValue != null)
}
fun setReadString(stringValue: String?) {
this.stringValue = stringValue
}
fun resetOutput() {
output.delete(0, output.length)
}
enum class Mode {
NON_INTERACTIVE,
INTERACTIVE,
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,28 +0,0 @@
package mtmc.emulator
import java.util.*
class MTMCIO {
var value: Int = 0
internal enum class Buttons(val mask: Int) {
UP(128),
DOWN(64),
LEFT(32),
RIGHT(16),
START(8),
SELECT(4),
B(2),
A(1)
}
fun keyPressed(key: String) {
val button = Buttons.valueOf(key.uppercase(Locale.getDefault()))
value = value or button.mask
}
fun keyReleased(key: String) {
val button = Buttons.valueOf(key.uppercase(Locale.getDefault()))
value = value and button.mask.inv()
}
}

View File

@@ -1,33 +0,0 @@
package mtmc.emulator
interface MTMCObserver {
fun consoleUpdated()
fun consolePrinting()
fun executionUpdated()
fun filesystemUpdated()
fun registerUpdated(register: Int, value: Int)
fun memoryUpdated(address: Int, value: Byte)
fun displayUpdated()
fun instructionFetched(instruction: Short)
fun beforeExecution(instruction: Short)
fun afterExecution(instruction: Short)
fun stepExecution()
fun computerReset()
fun requestCharacter()
fun requestInteger()
fun requestString()
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,76 +0,0 @@
package mtmc.emulator
enum class Register {
//=== user-facing registers
T0, // temp registers
T1,
T2,
T3,
T4,
T5,
A0, // arg registers
A1,
A2,
A3,
RV, // return value
RA, // return address
FP, // frame pointer
SP, // stack pointer
BP, // break pointer
PC,
//=== non-user-facing registers
IR, // instruction register
DR, // data register
CB, // code boundary
DB, // data boundary
IO, // I/O register
FLAGS; // flags register
companion object {
fun toInteger(reg: String): Int {
return valueOf(reg.uppercase()).ordinal
}
fun fromInteger(reg: Int): String {
return values()[reg].name.lowercase()
}
fun isWriteable(reg: Int): Boolean {
return reg in 0..15
}
fun isReadable(reg: Int): Boolean {
return reg in 0..15
}
private fun isTempRegister(reg: Int): Boolean {
return reg in 0..5
}
fun isWriteable(register: String): Boolean {
return try {
isWriteable(toInteger(register))
} catch (e: Exception) {
false
}
}
fun isReadable(register: String): Boolean {
return try {
isReadable(toInteger(register))
} catch (e: Exception) {
false
}
}
fun isTempRegister(register: String): Boolean {
return try {
isTempRegister(toInteger(register))
} catch (e: Exception) {
false
}
}
}
}

View File

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

View File

@@ -1,455 +0,0 @@
package mtmc.os
import mtmc.emulator.MonTanaMiniComputer
import mtmc.emulator.Register
import mtmc.os.SysCall.Companion.getValue
import mtmc.os.shell.Shell
import java.io.File
import java.io.IOException
import java.nio.file.Files
import java.util.*
import kotlin.math.max
import kotlin.math.min
import kotlin.text.Charsets.US_ASCII
class MTOS(private val computer: MonTanaMiniComputer) {
private var timer: Long = 0
var random: Random = Random()
// Editor support
var currentFile: String? = null
var currentFileMime: String? = null
var breakpoints: IntArray? = null
fun applyBreakpoints() {
if (computer.debugInfo == null || breakpoints == null) {
return
}
var debug = computer.debugInfo!!.originalLineNumbers
var name: String? = computer.debugInfo!!.originalFile
if (this.currentFileMime == "text/x-asm") {
debug = computer.debugInfo!!.assemblyLineNumbers
name = computer.debugInfo!!.assemblyFile
}
if (debug == null || name != currentFile) {
return
}
for (index in breakpoints!!.indices) {
val line = breakpoints!![index]
for (i in debug.indices) {
if (debug[i] == line) {
computer.setBreakpoint(i, true)
break
}
}
}
}
fun handleSysCall(syscallNumber: Short) {
if (syscallNumber == getValue("exit").toShort()) {
computer.setStatus(MonTanaMiniComputer.ComputerStatus.FINISHED)
} else if (syscallNumber == getValue("rint").toShort()) {
// rint
if (!computer.console.hasShortValue()) {
computer.notifyOfRequestInteger()
}
while (!computer.console.hasShortValue() && computer.getStatus() == MonTanaMiniComputer.ComputerStatus.EXECUTING) {
try {
Thread.sleep(10)
} catch (e: InterruptedException) {
}
}
val `val` = computer.console.readInt()
computer.setRegisterValue(Register.RV, `val`.toInt())
} else if (syscallNumber == getValue("wint").toShort()) {
// wint
val value = computer.getRegisterValue(Register.A0)
computer.console.writeInt(value)
} else if (syscallNumber == getValue("rchr").toShort()) {
if (!computer.console.hasShortValue()) {
computer.notifyOfRequestCharacter()
}
while (!computer.console.hasShortValue() && computer.getStatus() == MonTanaMiniComputer.ComputerStatus.EXECUTING) {
try {
Thread.sleep(10)
} catch (e: InterruptedException) {
}
}
val `val` = computer.console.readChar()
computer.setRegisterValue(Register.RV, `val`.code)
} else if (syscallNumber == getValue("wchr").toShort()) {
val value = computer.getRegisterValue(Register.A0)
computer.console.print("" + Char(value.toUShort()))
} else if (syscallNumber == getValue("rstr").toShort()) {
// rstr
val pointer = computer.getRegisterValue(Register.A0)
val maxLen = computer.getRegisterValue(Register.A1)
if (!computer.console.hasReadString()) {
computer.notifyOfRequestString()
}
while (!computer.console.hasReadString() && computer.getStatus() == MonTanaMiniComputer.ComputerStatus.EXECUTING) {
try {
Thread.sleep(10)
} catch (e: InterruptedException) {
}
}
val string = computer.console.readString()
val bytes = string!!.toByteArray(US_ASCII)
val bytesToRead = min(bytes.size, maxLen.toInt())
for (i in 0..<bytesToRead) {
val aByte = bytes[i]
computer.writeByteToMemory(pointer + i, aByte)
}
computer.setRegisterValue(Register.RV, bytesToRead)
} else if (syscallNumber == getValue("wstr").toShort()) {
// wstr
val pointer = computer.getRegisterValue(Register.A0)
val outputString = readStringFromMemory(pointer)
computer.console.print(outputString)
} else if (syscallNumber == getValue("printf").toShort()) {
val pointer = computer.getRegisterValue(Register.A0)
val initSP = computer.getRegisterValue(Register.A1)
val fmtString = readStringFromMemory(pointer)
val sb = StringBuilder()
var stackOff = 0
var i = 0
while (i < fmtString.length) {
var c = fmtString.get(i++)
if (c != '%') {
sb.append(c)
continue
}
if (i >= fmtString.length) break
c = fmtString.get(i++)
if (c == 'd') {
stackOff += 2
val v = computer.fetchWordFromMemory(initSP - stackOff).toInt()
sb.append(v)
} else if (c == 'c') {
stackOff += 2
val v = Char(computer.fetchWordFromMemory(initSP - stackOff).toUShort())
sb.append(v)
} else if (c == 's') {
stackOff += 2
val valuePointer = computer.fetchWordFromMemory(initSP - stackOff)
val s = readStringFromMemory(valuePointer)
sb.append(s)
} else {
sb.append('%').append(c)
}
}
computer.console.print(sb.toString())
computer.setRegisterValue(Register.RV, sb.length)
} else if (syscallNumber == getValue("atoi").toShort()) {
val pointer = computer.getRegisterValue(Register.A0)
val string = readStringFromMemory(pointer)
val split = string.trim { it <= ' ' }.split("\\s+".toRegex()).dropLastWhile { it.isEmpty() }
.toTypedArray()
val firstNum = split[0]
try {
val value = firstNum.toShort()
computer.setRegisterValue(Register.RV, value.toInt())
} catch (e: NumberFormatException) {
computer.setRegisterValue(Register.RV, 0)
}
} else if (syscallNumber == getValue("rnd").toShort()) {
// rnd
var low = computer.getRegisterValue(Register.A0)
var high = computer.getRegisterValue(Register.A1)
val temp: Short
if (low > high) {
temp = low
low = high
high = temp
}
computer.setRegisterValue(Register.RV, random.nextInt(low.toInt(), high + 1))
} else if (syscallNumber == getValue("sleep").toShort()) {
// sleep
val millis = computer.getRegisterValue(Register.A0)
try {
if (millis > 0) Thread.sleep(millis.toLong())
} catch (e: InterruptedException) {
throw RuntimeException(e)
}
} else if (syscallNumber == getValue("fbreset").toShort()) {
// fbreset
computer.display.reset()
} else if (syscallNumber == getValue("fbstat").toShort()) {
// fbstat
val x = computer.getRegisterValue(Register.A0)
val y = computer.getRegisterValue(Register.A1)
val `val` = computer.display.getPixel(x.toInt(), y.toInt())
computer.setRegisterValue(Register.RV, `val`.toInt())
} else if (syscallNumber == getValue("fbset").toShort()) {
// fbset
val x = computer.getRegisterValue(Register.A0)
val y = computer.getRegisterValue(Register.A1)
val color = computer.getRegisterValue(Register.A2)
computer.display.setPixel(x.toInt(), y.toInt(), color.toInt())
} else if (syscallNumber == getValue("fbline").toShort()) {
val startX = computer.getRegisterValue(Register.A0)
val startY = computer.getRegisterValue(Register.A1)
val endX = computer.getRegisterValue(Register.A2)
val endY = computer.getRegisterValue(Register.A3)
computer.display.drawLine(startX, startY, endX, endY)
} else if (syscallNumber == getValue("fbrect").toShort()) {
val startX = computer.getRegisterValue(Register.A0)
val startY = computer.getRegisterValue(Register.A1)
val width = computer.getRegisterValue(Register.A2)
val height = computer.getRegisterValue(Register.A3)
computer.display.drawRectangle(startX, startY, width, height)
} else if (syscallNumber == getValue("fbflush").toShort()) {
computer.display.sync()
} else if (syscallNumber == getValue("joystick").toShort()) {
computer.setRegisterValue(Register.RV, computer.iOState)
} else if (syscallNumber == getValue("scolor").toShort()) {
computer.display.setColor(computer.getRegisterValue(Register.A0))
} else if (syscallNumber == getValue("memcpy").toShort()) {
val fromPointer = computer.getRegisterValue(Register.A0)
val toPointer = computer.getRegisterValue(Register.A1)
val bytes = computer.getRegisterValue(Register.A2)
for (i in 0..<bytes) {
val b = computer.fetchByteFromMemory(fromPointer + i)
computer.writeByteToMemory(toPointer + i, b)
}
} else if (syscallNumber == getValue("rfile").toShort()) {
val fileNamePtr = computer.getRegisterValue(Register.A0)
val fileName = readStringFromMemory(fileNamePtr)
val file = File("disk" + computer.fileSystem.resolve(fileName))
if (!file.exists()) {
computer.setRegisterValue(Register.RV, 1)
return
}
val destination = computer.getRegisterValue(Register.A1)
val maxSize1 = computer.getRegisterValue(Register.A2)
val maxSize2 = computer.getRegisterValue(Register.A3)
val fileType = fileName.substring(fileName.lastIndexOf('.') + 1)
try {
// special handling for game-of-life cell files
if ("cells" == fileType) {
val str = Files.readString(file.toPath())
val lines = Arrays
.stream<String>(str.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray())
.filter { s: String? -> !s!!.startsWith("!") }
.toList()
val linesTotal = lines.size
val cappedLines = min(linesTotal, maxSize2.toInt())
for (lineNum in 0..<cappedLines) {
val line = lines.get(lineNum)
for (colNum in 0..<maxSize1) {
val offset = lineNum * maxSize1 + colNum
val byteOffset = offset / 8
val bitOffset = offset % 8
val currentVal = computer.fetchByteFromMemory(destination + byteOffset)
val mask = 1 shl bitOffset
val newVal: Byte
if (colNum < line.length && line.get(colNum) == 'O') {
newVal = (currentVal.toInt() or mask).toByte()
} else {
newVal = (currentVal.toInt() and mask.inv()).toByte()
}
computer.writeByteToMemory(destination + byteOffset, newVal)
}
}
} else {
val bytes = Files.readAllBytes(file.toPath())
for (i in 0..<maxSize1) {
val aByte = bytes[i]
computer.writeByteToMemory(destination + i, aByte)
}
}
computer.setRegisterValue(Register.RV, 0)
} catch (e: IOException) {
computer.setRegisterValue(Register.RV, -1)
e.printStackTrace() // debugging
}
} else if (syscallNumber == getValue("cwd").toShort()) {
val cwd = computer.fileSystem.listCWD().path
val destination = computer.getRegisterValue(Register.A0)
val maxSize = min(computer.getRegisterValue(Register.A1).toInt(), cwd.length + 1)
for (i in 0..<maxSize - 1) {
val aByte = cwd.get(i).code.toByte()
computer.writeByteToMemory(destination + i, aByte)
}
//TODO: Should this return the length with or without the null terminator?
computer.writeByteToMemory(destination + maxSize - 1, 0.toByte())
computer.setRegisterValue(Register.RV, maxSize - 1)
} else if (syscallNumber == getValue("chdir").toShort()) {
val pointer = computer.getRegisterValue(Register.A0)
val dir = readStringFromMemory(pointer)
if (computer.fileSystem.exists(dir)) {
computer.setRegisterValue(Register.RV, 0)
computer.fileSystem.setCWD(dir)
} else {
computer.setRegisterValue(Register.RV, 1)
}
} else if (syscallNumber == getValue("timer").toShort()) {
val value = computer.getRegisterValue(Register.A0)
if (value > 0) this.timer = System.currentTimeMillis() + value
computer.setRegisterValue(
Register.RV,
max(0, this.timer - System.currentTimeMillis()).toInt()
)
} else if (syscallNumber == getValue("drawimg").toShort()) {
val image = computer.getRegisterValue(Register.A0)
val x = computer.getRegisterValue(Register.A1)
val y = computer.getRegisterValue(Register.A2)
if (!computer.display.hasGraphic(image.toInt())) {
computer.setRegisterValue(Register.RV, 1)
return
}
computer.display.drawImage(image.toInt(), x.toInt(), y.toInt())
computer.setRegisterValue(Register.RV, 0)
} else if (syscallNumber == getValue("drawimgsz").toShort()) {
val image = computer.getRegisterValue(Register.A0)
val pointer = computer.getRegisterValue(Register.A1)
val x = computer.fetchWordFromMemory(pointer.toInt())
val y = computer.fetchWordFromMemory(pointer + 2)
val width = computer.fetchWordFromMemory(pointer + 4)
val height = computer.fetchWordFromMemory(pointer + 6)
if (!computer.display.hasGraphic(image.toInt())) {
computer.setRegisterValue(Register.RV, 1)
return
}
computer.display.drawImage(image.toInt(), x.toInt(), y.toInt(), width.toInt(), height.toInt())
computer.setRegisterValue(Register.RV, 0)
} else if (syscallNumber == getValue("drawimgclip").toShort()) {
val image = computer.getRegisterValue(Register.A0)
val source = computer.getRegisterValue(Register.A1)
val destination = computer.getRegisterValue(Register.A2)
val sx = computer.fetchWordFromMemory(source.toInt())
val sy = computer.fetchWordFromMemory(source + 2)
val sw = computer.fetchWordFromMemory(source + 4)
val sh = computer.fetchWordFromMemory(source + 6)
val dx = computer.fetchWordFromMemory(destination.toInt())
val dy = computer.fetchWordFromMemory(destination + 2)
val dw = computer.fetchWordFromMemory(destination + 4)
val dh = computer.fetchWordFromMemory(destination + 6)
if (!computer.display.hasGraphic(image.toInt())) {
computer.setRegisterValue(Register.RV, 1)
return
}
computer.display.drawImage(
image.toInt(),
sx.toInt(),
sy.toInt(),
sw.toInt(),
sh.toInt(),
dx.toInt(),
dy.toInt(),
dw.toInt(),
dh.toInt()
)
computer.setRegisterValue(Register.RV, 0)
} else if (syscallNumber == getValue("dirent").toShort()) {
val dirent = computer.getRegisterValue(Register.A0)
val command = computer.getRegisterValue(Register.A1)
val offset = computer.getRegisterValue(Register.A2)
val destination = computer.getRegisterValue(Register.A3)
val maxSize = computer.fetchWordFromMemory(dirent + 2)
val maxSizeOut = computer.fetchWordFromMemory(destination + 2)
val dir = readStringFromMemory(dirent)
val list: Array<File>
if (!computer.fileSystem.exists(dir)) {
computer.setRegisterValue(Register.RV, -1)
return
}
list = computer.fileSystem.getFileList(dir)
if (command.toInt() == 0) { // Count of files in the directory
computer.setRegisterValue(Register.RV, list.size)
}
if (command.toInt() == 1) {
if (offset < 0 || offset >= list.size) {
computer.setRegisterValue(Register.RV, -2)
return
}
val file = list[offset.toInt()]
val name = file.getName()
val size = min(maxSizeOut - 1, name.length)
computer.writeWordToMemory(destination.toInt(), if (file.isDirectory()) 1 else 0)
for (i in 0..<size) {
val aByte = name.get(i).code.toByte()
computer.writeByteToMemory(destination + 4 + i, aByte)
}
//TODO: Should this return the length with or without the null terminator?
computer.writeByteToMemory(destination + 4 + size, 0.toByte())
computer.setRegisterValue(Register.RV, min(maxSizeOut.toInt(), name.length) - 1)
}
} else if (syscallNumber == getValue("dfile").toShort()) {
val pointer = computer.getRegisterValue(Register.A0)
val path = readStringFromMemory(pointer)
if (computer.fileSystem.delete(path)) {
computer.setRegisterValue(Register.RV, 0)
} else {
computer.setRegisterValue(Register.RV, 1)
}
}
}
private fun readStringFromMemory(pointer: Short): String {
var length: Short = 0
while (computer.fetchByteFromMemory(pointer + length).toInt() != 0) {
length++
}
try {
val outputString = String(computer.memory, pointer.toInt(), length.toInt(), US_ASCII)
return outputString
} catch (ignored: StringIndexOutOfBoundsException) {
computer.setStatus(MonTanaMiniComputer.ComputerStatus.PERMANENT_ERROR)
return ""
}
}
fun processCommand(command: String) {
if (!command.trim { it <= ' ' }.isEmpty()) {
Shell.execCommand(command, computer)
}
}
fun loadFile(path: String?): File {
val fs = computer.fileSystem
val file = fs.getRealPath(path).toFile()
return file
}
}

View File

@@ -1,74 +0,0 @@
package mtmc.os
import java.util.*
enum class SysCall(value: Int) {
EXIT(0x00),
RINT(0x01),
WINT(0x02),
RSTR(0x03),
WCHR(0x04),
RCHR(0x05),
WSTR(0x06),
PRINTF(0x07),
ATOI(0x08),
RFILE(0x10),
WFILE(0x11),
CWD(0x12),
CHDIR(0x13),
DIRENT(0x14),
DFILE(0x15),
RND(0x20),
SLEEP(0x21),
TIMER(0x22),
FBRESET(0x30),
FBSTAT(0x31),
FBSET(0x32),
FBLINE(0x33),
FBRECT(0x34),
FBFLUSH(0x35),
JOYSTICK(0x3A),
SCOLOR(0x3B),
MEMCPY(0x40),
DRAWIMG(0x50),
DRAWIMGSZ(0x51),
DRAWIMGCLIP(0x52),
ERROR(0xFF);
val value: Byte
init {
this.value = value.toByte()
}
companion object {
fun isSysCall(call: String): Boolean {
try {
valueOf(call.uppercase(Locale.getDefault()))
return true
} catch (e: IllegalArgumentException) {
return false
}
}
@JvmStatic
fun getValue(call: String): Byte {
return valueOf(call.uppercase(Locale.getDefault())).value
}
fun getString(syscallCode: Byte): String? {
for (o in entries) {
if (o.value == syscallCode) {
return o.name.lowercase(Locale.getDefault())
}
}
return null
}
}
}

View File

@@ -1,60 +0,0 @@
package mtmc.os.exec
import com.google.gson.Gson
import mtmc.emulator.DebugInfo
import java.io.FileReader
import java.io.FileWriter
import java.io.IOException
import java.io.Reader
import java.io.Writer
import java.nio.file.Path
@JvmRecord
data class Executable(
val format: Format,
@JvmField val code: ByteArray,
@JvmField val data: ByteArray,
@JvmField val graphics: Array<ByteArray>,
val sourceName: String,
@JvmField val debugInfo: DebugInfo?
) {
enum class Format(val formatName: String) {
Orc1("orc1");
}
fun dump(): String? {
return Gson().toJson(this)
}
@Throws(IOException::class)
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 Gson().fromJson<Executable?>(exe, Executable::class.java)
}
@JvmStatic
@Throws(IOException::class)
fun load(path: Path): Executable? {
FileReader(path.toFile()).use { fw ->
return load(fw)
}
}
@Throws(IOException::class)
fun load(reader: Reader): Executable? {
val gson = Gson()
return gson.fromJson<Executable?>(reader, Executable::class.java)
}
}
}

View File

@@ -1,32 +0,0 @@
package mtmc.os.fs;
import mtmc.os.exec.Executable;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.spi.FileTypeDetector;
/**
*
* @author jbanes
*/
public class ExecutableFileTypeDetector extends FileTypeDetector {
@Override
public String probeContentType(Path path) throws IOException {
try(var in = Files.newInputStream(path)) {
if(in.read() != '{') return null;
}
try {
Executable.load(path);
return "text/mtmc16-bin";
} catch(IOException e) {
return null;
}
}
}

View File

@@ -1,199 +0,0 @@
package mtmc.os.fs;
import mtmc.emulator.MonTanaMiniComputer;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class FileSystem {
private String cwd = "/home";
private MonTanaMiniComputer computer;
static final Path DISK_PATH = Path.of(System.getProperty("user.dir"), "disk").toAbsolutePath();
public FileSystem(MonTanaMiniComputer computer) {
this.computer = computer;
initFileSystem();
}
private void initFileSystem() {
if (DISK_PATH.toFile().exists()) return;
// Make the disk/ directory
DISK_PATH.toFile().mkdirs();
try (var in = new ZipInputStream(getClass().getResourceAsStream("/disk.zip"))) {
ZipEntry entry;
File file;
byte[] data = new byte[4096];
int count;
while ((entry = in.getNextEntry()) != null) {
file = new File(entry.getName());
if (entry.isDirectory()) {
file.mkdirs();
} else {
file.getParentFile().mkdirs();
try (var out = new FileOutputStream(file)) {
while((count = in.read(data)) > 0) {
out.write(data, 0, count);
}
}
}
}
} catch(IOException e) {
e.printStackTrace();
}
}
private void notifyOfFileSystemUpdate() {
if (this.computer != null) {
computer.notifyOfFileSystemUpdate();
}
}
public String getCWD() {
return this.cwd;
}
public void setCWD(String cwd) {
this.cwd = resolve(cwd);
this.notifyOfFileSystemUpdate();
}
public boolean exists(String path) {
return new File(DISK_PATH.toFile(), resolve(path)).exists();
}
public boolean mkdir(String path) {
boolean success = new File(DISK_PATH.toFile(), resolve(path)).mkdir();
if (success) {
computer.notifyOfFileSystemUpdate();
}
return success;
}
public boolean delete(String path) {
boolean success = new File(DISK_PATH.toFile(), resolve(path)).delete();
if (success) {
computer.notifyOfFileSystemUpdate();
}
return success;
}
public String resolve(String filename) {
File root = DISK_PATH.toFile();
File directory = filename.startsWith("/") ? root : new File(root, cwd);
String[] path = filename.split("/");
for (String name : path) {
if (name.equals(".")) {
continue;
} else if (name.equals("..") && directory.equals(root)) {
continue;
} else if (name.equals("..")) {
directory = directory.getParentFile();
} else {
directory = new File(directory, name);
}
}
if(directory.equals(root)) {
return "/";
}
return directory.getAbsolutePath().substring(root.getAbsolutePath().length());
}
public Path getRealPath(String path) { // Resolves given path and returns /disk/ + path
String resolvedPath = resolve(path);
String slashGone = resolvedPath.length() > 0 ? resolvedPath.substring(1) : "";
File file = DISK_PATH.resolve(slashGone).toFile();
if (file.getAbsolutePath().length() < DISK_PATH.toFile().getAbsolutePath().length()) {
return DISK_PATH;
}
return file.toPath();
}
public File[] getFileList(String path) {
File resolvedPath = getRealPath(path).toFile();
if (!resolvedPath.isDirectory()) return new File[]{ resolvedPath };
return resolvedPath.listFiles();
}
public Listing listFiles(String path) {
File resolvedPath = getRealPath(path).toFile();
return new Listing(this, resolvedPath);
}
public Listing listCWD() {
return listFiles(cwd);
}
public Listing listRoot() {
return listFiles("/");
}
public void writeFile(String path, String contents) throws IOException {
Path filePath = getRealPath(path);
Files.writeString(filePath, contents);
}
public String readFile(String path) throws FileNotFoundException, IOException {
Path filePath = getRealPath(path);
var contents = Files.readString(filePath);
return contents;
}
public String getMimeType(String path) throws IOException {
var file = getRealPath(path);
var name = file.toFile().getName().toLowerCase();
var probed = Files.probeContentType(file);
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";
}
public InputStream openFile(String path) throws IOException {
var file = getRealPath(path).toFile();
return new FileInputStream(file);
}
public void saveFile(String path, InputStream contents) throws IOException {
var file = getRealPath(path).toFile();
byte[] data = new byte[4096];
int count;
try(var out = new FileOutputStream(file)) {
while((count = contents.read(data)) > 0) {
out.write(data, 0, count);
}
}
}
}

View File

@@ -1,52 +0,0 @@
package mtmc.os.fs;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Listing {
public final String path;
public final String name;
public final boolean directory;
public final boolean root;
private FileSystem fs;
private File file;
public Listing(FileSystem fs, File file) {
String root = FileSystem.DISK_PATH.toFile().getAbsolutePath().replace('\\', '/');
String path = file.getAbsolutePath().substring(root.length()).replace('\\', '/');
this.fs = fs;
this.file = file;
this.path = path.length() > 0 ? path : "/";
this.name = path.length() > 0 ? file.getName() : "/";
this.directory = file.isDirectory();
this.root = this.path.equals("/");
}
public Listing getParent() {
return new Listing(fs, file.getParentFile());
}
public List<Listing> list() {
if (!directory) {
return new ArrayList<>();
}
var list = new ArrayList<Listing>();
var children = file.listFiles();
Arrays.sort(children, (a, b) -> {
return a.getName().compareTo(b.getName());
});
for (File child : children) {
list.add(new Listing(fs, child));
}
return list;
}
}

View File

@@ -1,30 +0,0 @@
package mtmc.os.fs;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.spi.FileTypeDetector;
/**
*
* @author jbanes
*/
public class PlainTextFileTypeDetector extends FileTypeDetector {
@Override
public String probeContentType(Path path) throws IOException {
int c;
try(var in = Files.newInputStream(path)) {
// Detect non-ASCII characters
while ((c = in.read()) >= 0) {
if (c < 9 || c > 126) {
return null;
}
}
}
return "text/plain";
}
}

View File

@@ -1,13 +0,0 @@
package mtmc.os.shell
import mtmc.emulator.MonTanaMiniComputer
import mtmc.tokenizer.MTMCTokenizer
abstract class ShellCommand {
@Throws(Exception::class)
abstract fun exec(tokens: MTMCTokenizer, computer: MonTanaMiniComputer)
abstract val help: String?
fun usageException() {
throw UsageException(this)
}
}

View File

@@ -1,45 +0,0 @@
package mtmc.os.shell.builtins
import mtmc.asm.Assembler
import mtmc.emulator.MonTanaMiniComputer
import mtmc.os.fs.FileSystem
import mtmc.os.shell.ShellCommand
import mtmc.tokenizer.MTMCTokenizer
import java.nio.file.Files
import java.nio.file.Path
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

@@ -1,85 +0,0 @@
package mtmc.os.shell.builtins
import mtmc.emulator.MTMCDisplay
import mtmc.emulator.MonTanaMiniComputer
import mtmc.os.shell.ShellCommand
import mtmc.tokenizer.MTMCToken
import mtmc.tokenizer.MTMCTokenizer
import java.io.File
import java.util.*
import javax.imageio.ImageIO
class DisplayCommand : ShellCommand() {
var random: Random = Random()
@Throws(Exception::class)
public override fun exec(tokens: MTMCTokenizer, computer: MonTanaMiniComputer) {
if (tokens.match(MTMCToken.TokenType.IDENTIFIER)) {
val option = tokens.consumeAsString()
when (option) {
"fuzz" -> {
for (row in 0..<MTMCDisplay.ROWS) {
for (col in 0..<MTMCDisplay.COLS) {
computer.display.setPixel(col, row, random.nextInt(0, 4).toShort().toInt())
}
}
}
"reset" -> {
computer.display.reset()
}
"invert" -> {
for (row in 0..<MTMCDisplay.ROWS) {
for (col in 0..<MTMCDisplay.COLS) {
val color = computer.display.getPixel(col, row)
computer.display.setPixel(col, row, 3.toShort() - color)
}
}
}
"image" -> {
if (tokens.more()) {
val imagePath = tokens.collapseTokensAsString()
val file = computer.oS.loadFile(imagePath)
val img = ImageIO.read(file)
computer.display.loadScaledImage(img)
} else {
usageException()
}
}
else -> usageException()
}
} else if (tokens.match(MTMCToken.TokenType.INTEGER)) {
val row = tokens.consumeAsInteger()
val col = tokens.require(
mtmc.tokenizer.MTMCToken.TokenType.INTEGER,
java.lang.Runnable { this.usageException() })!!.intValue()
val color = tokens.require(
mtmc.tokenizer.MTMCToken.TokenType.INTEGER,
java.lang.Runnable { this.usageException() })!!.intValue()
computer.display.setPixel(row, col, color)
} else {
usageException()
}
computer.display.sync()
}
override val help: String
get() = """
disp [options] - updates the display
fuzz - displays random colors
reset - resets the display
invert - inverts the display
image <file> - loads the given image into the display
<x> <y> <color> - sets the given pixel to the given color [0-3]
""".trimIndent()
companion object {
private fun loadFile(imagePath: String?): File {
val file = File("disk/" + imagePath)
return file
}
}
}

View File

@@ -1,15 +0,0 @@
package mtmc.os.shell.builtins
import mtmc.emulator.MonTanaMiniComputer
import mtmc.os.shell.ShellCommand
import mtmc.tokenizer.MTMCTokenizer
class ExitCommand : ShellCommand() {
public override fun exec(tokens: MTMCTokenizer, computer: MonTanaMiniComputer) {
computer.console.println("Goodbye!")
System.exit(1)
}
override val help: String
get() = "exit - exits the system"
}

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