generated from rnentjes/kotlin-server-web-undertow
Remove legacy JVM-specific file system, shell, and related implementations; migrate to platform-agnostic and common main modules.
This commit is contained in:
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package mtmc.asm
|
||||
|
||||
interface HasLocation {
|
||||
var location: Int
|
||||
var sizeInBytes: Int
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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>"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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?)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user