Refactor: Restructure project package hierarchy and add initial implementation for assembler instructions, shell commands, and exception handling.

This commit is contained in:
2025-08-13 21:42:49 +02:00
parent b103631133
commit 12027fe740
135 changed files with 10835 additions and 31 deletions

1
.idea/.name generated
View File

@@ -1 +0,0 @@
template

View File

@@ -0,0 +1,8 @@
<component name="ArtifactManager">
<artifact type="jar" name="mtmc-web-js-0.1.0-SNAPSHOT">
<output-path>$PROJECT_DIR$/build/libs</output-path>
<root id="archive" name="mtmc-web-js-0.1.0-SNAPSHOT.jar">
<element id="module-output" name="mtmc-web.jsMain" />
</root>
</artifact>
</component>

View File

@@ -0,0 +1,8 @@
<component name="ArtifactManager">
<artifact type="jar" name="mtmc-web-jvm-0.1.0-SNAPSHOT">
<output-path>$PROJECT_DIR$/build/libs</output-path>
<root id="archive" name="mtmc-web-jvm-0.1.0-SNAPSHOT.jar">
<element id="module-output" name="mtmc-web.jvmMain" />
</root>
</artifact>
</component>

2
.idea/misc.xml generated
View File

@@ -3,7 +3,7 @@
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="corretto-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
<component name="accountSettings">

View File

@@ -1,5 +1,6 @@
@file:OptIn(ExperimentalDistributionDsl::class)
import org.codehaus.groovy.tools.shell.util.Logger.io
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalDistributionDsl
plugins {
@@ -50,6 +51,9 @@ kotlin {
implementation("nl.astraeus:simple-jdbc-stats:1.6.1") {
exclude(group = "org.slf4j", module = "slf4j-api")
}
implementation("io.pebbletemplates:pebble:3.2.3")
implementation("com.google.code.gson:gson:2.12.1")
}
}
val jvmTest by getting {

View File

@@ -1,5 +1,5 @@
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0"
}
val REPO_NAME = "dummy so the gitea template compiles, please remove"
rootProject.name = "mtmc-web"

View File

@@ -1,11 +1,10 @@
package nl.astraeus.tmpl
package mtmc
import kotlinx.browser.document
import kotlinx.html.div
import nl.astraeus.komp.HtmlBuilder
import nl.astraeus.komp.Komponent
class HelloKomponent : Komponent() {
override fun HtmlBuilder.render() {
div {

View File

@@ -1,4 +1,4 @@
package nl.astraeus.tmpl
package mtmc
import com.zaxxer.hikari.HikariConfig
import io.undertow.Undertow
@@ -8,8 +8,8 @@ import io.undertow.server.handlers.encoding.ContentEncodingRepository
import io.undertow.server.handlers.encoding.EncodingHandler
import io.undertow.server.handlers.encoding.GzipEncodingProvider
import nl.astraeus.logger.Logger
import nl.astraeus.tmpl.db.Database
import nl.astraeus.tmpl.web.RequestHandler
import mtmc.db.Database
import mtmc.web.RequestHandler
val log = Logger()

View File

@@ -0,0 +1,8 @@
package mtmc
val SERVER_PORT = 4001
val JDBC_PORT = 4002
val pageTitle = "mtmc-web"
val itemUrl = "mtmc-web"
val repoName = "mtmc-web"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
package nl.astraeus.tmpl.db
package mtmc.db
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource

View File

@@ -1,6 +1,6 @@
package nl.astraeus.tmpl.db
package mtmc.db
import nl.astraeus.tmpl.log
import mtmc.log
import java.sql.Connection
import java.sql.SQLException
import java.sql.Timestamp

View File

@@ -0,0 +1,45 @@
package mtmc.emulator
import java.util.*
import java.util.regex.Pattern
@JvmRecord
data class DebugInfo(
val debugStrings: MutableList<String>,
val assemblyFile: String?,
val assemblySource: String,
val assemblyLineNumbers: IntArray,
val originalFile: String,
val originalLineNumbers: IntArray,
val globals: Array<GlobalInfo>,
val locals: Array<Array<LocalInfo?>>
) {
fun handleDebugString(debugIndex: Short, monTanaMiniComputer: MonTanaMiniComputer) {
val debugString = debugStrings!!.get(debugIndex.toInt())
val compile = Pattern.compile("(\\$[a-zA-Z][a-zA-Z0-9])")
val matcher = compile.matcher(debugString)
val formattedString = StringBuilder()
var start = 0
var end: Int
while (matcher.find()) {
val match = matcher.group().substring(1)
try {
end = matcher.start()
formattedString.append(debugString, start, end)
val register = Register.valueOf(match.uppercase(Locale.getDefault()))
formattedString.append(monTanaMiniComputer.getRegisterValue(register).toInt())
start = matcher.end()
} catch (e: Exception) {
formattedString.append(match)
}
}
formattedString.append(debugString.substring(start))
println("DEBUG[" + System.nanoTime() + "] : " + formattedString)
}
@JvmRecord
data class GlobalInfo(val name: String?, val location: Int, val type: String?)
@JvmRecord
data class LocalInfo(val name: String?, val offset: Int, val type: String?)
}

View File

@@ -0,0 +1,71 @@
package mtmc.emulator
import mtmc.emulator.MonTanaMiniComputer.ComputerStatus
import kotlin.math.max
/**
*
* @author jbanes
*/
class MTMCClock
(private val computer: MonTanaMiniComputer) {
fun run() {
var instructions: Long = 0
var ips: Long = 0
var expected: Long = 0
var virtual: Long = 0
var startTime = System.currentTimeMillis()
var deltaStart: Long
var delta: Long
var speed: Long = 0
var pulse: Long
var ms: Long = 10
while (computer.getStatus() == ComputerStatus.EXECUTING) {
speed = max(computer.speed, 0).toLong()
pulse = (if (speed <= 0) 1000000 else max(speed / 100, 1))
ms = (if (pulse < 10) 1000 / speed else 10)
deltaStart = System.currentTimeMillis()
delta = ms - (System.currentTimeMillis() - deltaStart)
/* We've lost more than a second. Recalibrate. */
if ((expected - virtual) > pulse * 100) {
startTime = deltaStart
virtual = 0
}
/* Throttles to every 10ms, but "catches up" if we're behind */
if (delta > 0 && (expected - virtual) < pulse && speed != 0L) {
try {
Thread.sleep(delta)
} catch (e: InterruptedException) {
}
}
instructions += computer.pulse(pulse)
virtual += pulse
ips = (virtual * 1000) / max(1, System.currentTimeMillis() - startTime)
expected = (System.currentTimeMillis() - startTime) * speed / 1000
}
System.err.println("Executed " + instructions + " instructions at a rate of " + ips + " ips (speed = " + speed + ")")
}
fun step() {
computer.fetchAndExecute()
computer.fetchCurrentInstruction()
computer.notifyOfStepExecution()
}
fun back() {
computer.rewind()
computer.fetchCurrentInstruction()
computer.notifyOfStepExecution()
}
}

View File

@@ -0,0 +1,131 @@
package mtmc.emulator
import mtmc.os.shell.Shell
import mtmc.tokenizer.MTMCScanner
import mtmc.tokenizer.MTMCToken
import java.io.Console
class MTMCConsole(private val computer: MonTanaMiniComputer) {
var mode: Mode = Mode.NON_INTERACTIVE
var sysConsole: Console? = null
// non-interactive data
private val output = StringBuffer()
private var shortValueSet = false
private var shortValue: Short = 0
private var stringValue: String? = null
// TODO invert so shell is driving and console is just IO
fun start() {
mode = Mode.INTERACTIVE
sysConsole = System.console()
Shell.printShellWelcome(computer)
while (true) {
val cmd = sysConsole!!.readLine("mtmc > ")
computer.oS.processCommand(cmd)
}
}
fun println(x: String) {
print(x)
print("\n")
}
fun print(x: String) {
output.append(x)
if (mode == Mode.INTERACTIVE) {
kotlin.io.print(x)
} else {
if (x.contains("\n")) {
computer.notifyOfConsoleUpdate()
} else {
computer.notifyOfConsolePrinting()
}
}
}
fun readChar(): Char {
if (mode == Mode.INTERACTIVE) {
val tokens = MTMCScanner(sysConsole!!.readLine(), "#").tokenize()
val token = tokens.first()
assert(token.type === MTMCToken.TokenType.CHAR)
return token.charValue()
} else {
this.shortValueSet = false
return Char(this.shortValue.toUShort())
}
}
fun readInt(): Short {
if (mode == Mode.INTERACTIVE) {
return sysConsole!!.readLine().toShort()
} else {
this.shortValueSet = false
return shortValue
}
}
fun hasShortValue(): Boolean {
return (mode == Mode.INTERACTIVE || shortValueSet)
}
fun setShortValue(shortValue: Short) {
this.shortValue = shortValue
this.shortValueSet = true
}
fun setCharValue(charValue: Char) {
this.shortValue = charValue.code.toShort()
this.shortValueSet = true
}
fun getOutput(): String {
return output.toString()
}
fun consumeLines(): String {
val index = output.lastIndexOf("\n")
val text = if (index >= 0) output.substring(0, index + 1) else ""
if (index >= 0) {
output.delete(0, index + 1)
}
return text
}
fun writeInt(value: Short) {
print(value.toString() + "")
}
fun setStringValue(stringValue: String?) {
this.stringValue = stringValue
}
fun readString(): String? {
if (mode == Mode.INTERACTIVE) {
return sysConsole!!.readLine()
} else {
val stringValue = this.stringValue
this.stringValue = null
return stringValue
}
}
fun hasReadString(): Boolean {
return (mode == Mode.INTERACTIVE || stringValue != null)
}
fun setReadString(stringValue: String?) {
this.stringValue = stringValue
}
fun resetOutput() {
output.delete(0, output.length)
}
enum class Mode {
NON_INTERACTIVE,
INTERACTIVE,
}
}

File diff suppressed because one or more lines are too long

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -0,0 +1,19 @@
package mtmc.lang;
public class CompilationException extends Exception {
protected Span span;
public CompilationException(String message, Span span) {
super(message);
this.span = span;
}
public CompilationException(CompilationException parent, String message) {
super(message, parent);
this.span = parent.span;
}
public Span getSpan() {
return span;
}
}

View File

@@ -0,0 +1,7 @@
package mtmc.lang;
import mtmc.os.exec.Executable;
public interface Language {
Executable compileExecutable(String filename, String source) throws ParseException, CompilationException;
}

View File

@@ -0,0 +1,55 @@
package mtmc.lang;
public record Location(int index) {
public static int[] getLineNos(String source, int... indices) {
int[] out = new int[indices.length];
int index = 0, line = 1;
while (index < source.length()) {
for (int i = 0; i < indices.length; i++) {
if (indices[i] == index) {
out[i] = line;
}
}
int cp = source.charAt(index);
if (cp == '\n') {
line += 1;
}
index += Character.charCount(cp);
}
return out;
}
public LineInfo getLineInfo(String source) {
int index = 0, lineStart = 0;
int lineno = 1;
int column = 1;
while (index < source.length()) {
if (index == this.index) {
break;
}
int cp = source.charAt(index);
if (cp == '\n') {
lineno += 1;
lineStart = index + 1;
} else {
column += 1;
}
index += Character.charCount(cp);
}
while (index < source.length()) {
int cp = source.charAt(index);
index += Character.charCount(cp);
if (cp == '\n') break;
}
String line = source.substring(lineStart, index);
return new LineInfo(lineno, column, line);
}
public record LineInfo(int lineno, int column, String line) {}
}

View File

@@ -0,0 +1,43 @@
package mtmc.lang;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class ParseException extends Exception {
public final List<Message> messages;
public ParseException(Message message, Message ...rest) {
var messages = new ArrayList<Message>(1 + rest.length);
messages.add(message);
messages.addAll(Arrays.asList(rest));
this.messages = Collections.unmodifiableList(messages);
}
public ParseException(ParseException parent, Message message, Message ...rest) {
var messages = new ArrayList<Message>( 1 + rest.length + parent.messages.size());
messages.add(message);
messages.addAll(Arrays.asList(rest));
messages.addAll(parent.messages);
this.messages = Collections.unmodifiableList(messages);
}
public record Message(Span span, String message) {
public Message(Token token, String message) {
this(Span.of(token), message);
}
public Token start() {
return span.start();
}
public Token end() {
return span.end();
}
}
public String report(String source) {
return "TODO: I ain't no snitch";
}
}

View File

@@ -0,0 +1,17 @@
package mtmc.lang;
public record Span(Token start, Token end) {
public static Span of(Token token) {
return new Span(token, token);
}
public static Span of(Token start, Token end) {
return new Span(start, end);
}
public boolean isOnSingleLine(String source) {
int[] lines = Location.getLineNos(source, start.start(), end.end());
return lines[0] == lines[1];
}
}

View File

@@ -0,0 +1,15 @@
package mtmc.lang;
public interface Token {
Location getStart();
Location getEnd();
String getContent();
default int start() {
return getStart().index();
}
default int end() {
return getEnd().index();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,47 @@
package mtmc.lang.sea;
import mtmc.lang.CompilationException;
import mtmc.lang.Language;
import mtmc.lang.ParseException;
import mtmc.lang.sea.ast.Error;
import mtmc.lang.sea.ast.Unit;
import mtmc.os.exec.Executable;
public class SeaLanguage implements Language {
@Override
public Executable compileExecutable(String filename, String source) throws ParseException, CompilationException {
var tokens = Token.tokenize(source);
var parser = new SeaParser(filename, source, tokens);
Unit program = parser.parseUnit();
var errors = program.collectErrors();
if (!errors.isEmpty()) {
StringBuilder sb = new StringBuilder();
for (Error error : errors) {
reportError(source, sb, error.exception());
}
throw new RuntimeException(sb.toString());
}
var compiler = new SeaCompiler(program);
return compiler.compile();
}
private static void reportError(String src, StringBuilder sb, ParseException e) {
sb.append("Error:\n");
for (var msg : e.messages) {
var lo = Token.getLineAndOffset(src, msg.start().start());
int lineNo = lo[0];
int column = lo[1];
var line = Token.getLineFor(src, msg.start().start());
String prefix = " %03d:%03d | ".formatted(lineNo, column);
String info = " ".repeat(prefix.length() - 2) + "| ";
sb.append(info).append(msg.message()).append('\n');
sb.append(prefix).append(line).append('\n');
sb
.repeat(' ', prefix.length() + column - 1)
.repeat('^', Math.max(1, msg.end().end() - msg.start().start()));
sb.append("\n\n");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,231 @@
package mtmc.lang.sea;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public sealed interface SeaType {
default int size() {
if (this == CHAR) return 2;
if (this == INT) return 2;
if (this == VOID) return 0;
if (this instanceof Pointer) return 2;
if (this instanceof Struct struct) {
int size = 0;
for (Map.Entry<String, SeaType> fieldSet : struct.fields.entrySet()) {
SeaType type = fieldSet.getValue();
size += type.size();
}
return size;
}
throw new IllegalStateException("sizeof " + getClass().getName() + " is undefined");
}
default boolean isArithmetic() {
return this == CHAR || this == INT;
}
default boolean isIntegral() {
return this == CHAR || this == INT || this instanceof Pointer;
}
default boolean isStructure() {
return this instanceof Struct;
}
default boolean isInt() {
return this == INT;
}
default boolean isChar() {
return this == CHAR;
}
default boolean isVoid() {
return this == VOID;
}
default boolean isAnIntegralPointer() {
return this instanceof Pointer(SeaType component) && component.isIntegral();
}
default boolean isAPointerToAnInt() {
return this instanceof Pointer(SeaType component) && component.isInt();
}
default boolean isAPointerTo(SeaType inner) {
return this instanceof Pointer(SeaType it) && it.equals(inner);
}
default boolean isAPointer() {
return this instanceof Pointer;
}
default boolean isFunc() {
return this instanceof Func;
}
default SeaType componentType() {
return ((Pointer) this).component;
}
default SeaType resultType() {
return ((Func) this).result();
}
default void checkConversionTo(SeaType other) throws ConversionError {
if (this.isVoid() || other.isVoid()) throw new ConversionError(this, other, "void is not assignable to void");
if (this.isArithmetic() && other.isArithmetic()) return;
if (this.isAPointer() && other.isAPointer()) return;
if (this instanceof Initializer initializer && other instanceof Struct s) {
// this is kinda complex
// the way this should work is we process named assignments and then based on the last
// index of the named assignments, we start putting in values
// the challenge here is that the blob may have too many values or may initialize them improperly
if (initializer.values.size() != s.fields.size()) {
throw new ConversionError(this, other, "initializer has too many or too few values");
}
int i = 0;
for (Map.Entry<String, SeaType> entry : s.fields.entrySet()) {
var name = entry.getKey();
var ty = entry.getValue();
var valueTy = initializer.values.get(i);
try {
valueTy.checkConversionTo(ty);
} catch (ConversionError error) {
throw new ConversionError(ty, valueTy,
"value cannot be assigned to " + ty.repr() + " for '" + name + "'", error);
}
i += 1;
}
return;
}
if (!this.equals(other)) {
throw new ConversionError(this, other, this.repr() + " is not convertible to " + other.repr());
}
}
class ConversionError extends Exception {
public final SeaType fromType, toType;
private ConversionError(SeaType fromType, SeaType toType, String message) {
super(message);
this.fromType = fromType;
this.toType = toType;
}
private ConversionError(SeaType fromType, SeaType toType, String message, ConversionError parent) {
super(message, parent);
this.fromType = fromType;
this.toType = toType;
}
}
default boolean isCastableTo(SeaType target) {
if (target.isVoid()) return true;
if (this.isAPointer() && target.isInt()) return true;
if (this.isArithmetic() && target.isArithmetic()) return true;
return this.equals(target);
}
default String repr() {
if (this instanceof Pointer p) {
if (p.baseType() instanceof Func(List<SeaType> params, boolean isVararg, SeaType result)) {
var s = new StringBuilder();
s.append(result.repr()).append("(*");
var x = p.component;
while (x instanceof Pointer p2) {
x = p2.component;
s.append("*");
}
s.append(")(");
int i = 0;
for (var param : params) {
if (i > 0) s.append(", ");
s.append(param.repr());
i = i + 1;
}
s.append(")");
return s.toString();
} else {
return p.component.repr() + "*";
}
}
if (this == CHAR) return "char";
if (this == INT) return "int";
if (this == VOID) return "void";
if (this instanceof Func(List<SeaType> params, boolean isVararg, SeaType result)) {
var s = new StringBuilder();
s.append(result.repr());
s.append("(");
int i = 0;
for (var param : params) {
if (i > 0) s.append(", ");
s.append(param.repr());
i = i + 1;
}
s.append(")");
return s.toString();
}
if (this instanceof Initializer(List<SeaType> values)) {
var s = new StringBuilder();
s.append("{");
for (int i = 0; i < values.size(); i++) {
if (i > 0) s.append(", ");
s.append(values.get(i).repr());
}
s.append("}");
return s.toString();
}
if (this instanceof Struct s) {
return "struct " + s.name;
}
throw new UnsupportedOperationException("unknown type " + this);
}
SeaType CHAR = new Primitive("char");
SeaType INT = new Primitive("int");
SeaType VOID = new Primitive("void");
record Pointer(SeaType component) implements SeaType {
SeaType baseType() {
var ty = component;
while (ty instanceof Pointer(SeaType c)) {
ty = c;
}
return ty;
}
}
final class Primitive implements SeaType {
// this is purely for debug info lmfao
public final String name;
private Primitive(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
record Func(List<SeaType> params, boolean isVararg, SeaType result) implements SeaType {
public Func(List<SeaType> params, SeaType result) {
this(params, false, result);
}
}
record Struct(String name, LinkedHashMap<String, SeaType> fields) implements SeaType {
public SeaType field(String name) {
return fields.get(name);
}
}
record Initializer(List<SeaType> values) implements SeaType {
}
}

View File

@@ -0,0 +1,59 @@
package mtmc.lang.sea;
import mtmc.lang.sea.ast.DeclarationFunc;
import mtmc.lang.sea.ast.DeclarationVar;
import mtmc.lang.sea.ast.StatementVar;
import mtmc.lang.sea.ast.TypeDeclaration;
public class Symbol {
public final String name;
public final SeaType type;
public final TypeDeclaration typeDecl;
public final boolean isParam, isGlobal;
public Symbol(DeclarationFunc.Param param) {
this.name = param.name.content();
this.type = param.type.type();
this.typeDecl = null;
this.isParam = true;
this.isGlobal = false;
}
public Symbol(DeclarationVar decl) {
this.name = decl.name();
this.type = decl.type.type();
this.typeDecl = null;
this.isParam = false;
this.isGlobal = true;
}
public Symbol(StatementVar stmt) {
this.name = stmt.name();
this.type = stmt.type.type();
this.typeDecl = null;
this.isParam = false;
this.isGlobal = false;
}
public Symbol(DeclarationFunc func) {
this.name = func.name.content();
this.type = func.type();
this.typeDecl = null;
this.isParam = false;
this.isGlobal = true;
}
public Symbol(TypeDeclaration declaration) {
this.name = declaration.name();
this.type = null;
this.typeDecl = declaration;
this.isParam = false;
this.isGlobal = false;
}
public boolean isAddressable() {
if (this.typeDecl != null) throw new IllegalStateException("cannot address non-data symbol");
if (this.isParam) return false; // parameters are not addressable!
return true;
}
}

View File

@@ -0,0 +1,426 @@
package mtmc.lang.sea;
import mtmc.lang.Location;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public record Token(
Type type,
@NotNull
String content,
int start,
int end
) implements mtmc.lang.Token {
public static final Token SOF = new Token(Type.SOF, "", 0, 0);
public static final Token EOF = new Token(Type.EOF, "", Integer.MAX_VALUE, Integer.MAX_VALUE);
public enum Type {
// Special
LIT_INT(null),
LIT_STR(null),
LIT_CHAR(null),
LIT_IDENT(null),
KW_TYPEDEF("typedef"),
KW_STRUCT("struct"),
KW_IF("if"),
KW_ELSE("else"),
KW_FOR("for"),
KW_WHILE("while"),
KW_DO("do"),
KW_GOTO("goto"),
KW_CONTINUE("continue"),
KW_BREAK("break"),
KW_RETURN("return"),
KW_SIZEOF("sizeof"),
KW_INT("int"),
KW_CHAR("char"),
KW_VOID("void"),
SOF(null),
EOF(null),
// Groups
LEFT_PAREN("("),
RIGHT_PAREN(")"),
LEFT_BRACKET("["),
RIGHT_BRACKET("]"),
LEFT_BRACE("{"),
RIGHT_BRACE("}"),
// Simple Punct
DOT3("..."),
DOT("."),
SEMICOLON(";"),
COMMA(","),
COLON(":"),
TILDE("~"),
QUESTION("?"),
PLUS2("++"),
PLUS_EQ("+="),
PLUS("+"),
DASH2("--"),
DASH_EQ("-="),
ARROW("->"),
DASH("-"),
STAR_EQ("*="),
STAR("*"),
SLASH_EQ("/="),
SLASH("/"),
PERCENT_EQ("%="),
PERCENT("%"),
AMPERSAND2("&&"),
AMPERSAND_EQ("&="),
AMPERSAND("&"),
BAR2("||"),
BAR_EQ("|="),
BAR("|"),
CARET("^"),
CARET_EQ("^="),
LEFT_ARROW2_EQ("<<="),
LEFT_ARROW2("<<"),
LEFT_ARROW_EQ("<="),
LEFT_ARROW("<"),
RIGHT_ARROW2_EQ(">>="),
RIGHT_ARROW2(">>"),
RIGHT_ARROW_EQ(">="),
RIGHT_ARROW(">"),
EQUAL2("=="),
EQUAL("="),
BANG_EQ("!="),
BANG("!");
public final String lex;
public static final Type[] PUNCT;
static {
List<Type> list = new ArrayList<>();
for (Type t : Type.values()) {
if (t.lex != null) {
list.add(t);
}
}
PUNCT = list.toArray(new Type[0]);
}
public static final Type[] KEYWORDS;
static {
List<Type> list = new ArrayList<>();
for (Type t : Type.values()) {
if (t.name().startsWith("KW_")) {
list.add(t);
}
}
KEYWORDS = list.toArray(new Type[0]);
}
Type(String lex) {
this.lex = lex;
}
}
public static int[] getLineAndOffset(String src, int index) {
int line = 1;
int column = 1;
for (int i = 0; i < index && i < src.length(); i++) {
char c = src.charAt(i);
if (c == '\n') {
line = line + 1;
column = 1;
} else {
column = column + 1;
}
}
return new int[]{line, column};
}
public static String getLineFor(String src, int index) {
int start = 0;
for (int i = Math.min(index, src.length() - 1); i >= 0; i--) {
if (src.charAt(i) == '\n') {
start = i + 1;
break;
}
}
int end = src.length();
for (int i = index; i < src.length(); i++) {
if (src.charAt(i) == '\n') {
break;
}
end = i + 1;
}
return src.substring(start, end);
}
public static String highlight(String src, int start, int end) {
var s = getLineAndOffset(src, start);
var e = getLineAndOffset(src, end);
int lineStart;
if (s[0] != e[0]) {
lineStart = 0;
} else {
lineStart = s[1] - 1;
}
int lineEnd = e[1] - 1;
String line = getLineFor(src, end);
StringBuilder result = new StringBuilder();
int off = 0;
if (lineStart > 10) {
result.append("... ");
off += 4;
result.append(line.substring(lineStart, lineEnd));
} else {
result.append(line.substring(0, lineEnd));
}
result.append('\n');
result.repeat(' ', off + lineStart);
if (start == Integer.MAX_VALUE) {
result.append("^ (at EOL)");
} else {
result.repeat('^', lineEnd - lineStart);
result.append(" (here)");
}
return result.toString();
}
public static List<Token> tokenize(String src) throws TokenizeException {
List<Token> tokens = new ArrayList<>();
int offset = 0;
do {
Token token = tokenizeOne(src, offset);
if (token == null) break;
tokens.add(token);
offset = token.end();
} while (true);
return tokens;
}
private static boolean match(String str, int start, String token) {
if (str == null) return false;
if (str.length() - start < token.length()) return false;
for (int i = 0; i < token.length(); i++) {
char c = str.charAt(start + i);
char d = token.charAt(i);
if (c != d) return false;
}
return true;
}
private static boolean match(String str, int start, char c) {
if (str == null) return false;
if (str.length() - start < Character.charCount(c)) return false;
return str.charAt(start) == c;
}
public static Token tokenizeOne(String src, int offset) throws TokenizeException {
while (offset < src.length()) {
if (Character.isWhitespace(src.charAt(offset))) {
offset += Character.charCount(src.charAt(offset));
} else if (match(src, offset, "//")) {
offset += 2;
while (offset < src.length()) {
char c = src.charAt(offset);
offset += Character.charCount(c);
if (c == '\n') {
break;
}
}
} else if (match(src, offset, "/*")) {
offset += 2;
while (offset < src.length()) {
if (match(src, offset, "*/")) {
offset += 2;
break;
} else {
offset += Character.charCount(src.charAt(offset));
}
}
} else {
break;
}
}
if (offset >= src.length()) return null;
int start = offset;
Type type;
String content = null;
char c = src.charAt(offset);
if (Character.isDigit(c)) {
do {
offset += Character.charCount(src.charAt(offset));
} while (offset < src.length() && Character.isDigit(src.charAt(offset)));
content = src.substring(start, offset);
type = Type.LIT_INT;
} else if (Character.isLetter(c) || c == '_') {
do {
offset += Character.charCount(src.charAt(offset));
} while (offset < src.length() && (Character.isLetter(src.charAt(offset)) || Character.isDigit(src.charAt(offset)) || src.charAt(offset) == '_'));
content = src.substring(start, offset);
type = Type.LIT_IDENT;
for (var ty : Type.KEYWORDS) {
if (content.equals(ty.lex)) {
type = ty;
break;
}
}
} else if (c == '\'') {
offset += Character.charCount(c);
char d = src.charAt(offset);
offset += Character.charCount(d);
if (d == '\\') {
if (offset >= src.length()) throw new TokenizeException("invalid character escape " + d, start, offset);
d = src.charAt(offset);
offset += Character.charCount(d);
content = switch (d) {
case 'n':
yield "\n";
case 'r':
yield "\r";
case 't':
yield "\t";
case '\\':
yield "\\";
case '\'':
yield "'";
case '"':
yield "\"";
case '?':
yield "?";
default:
throw new TokenizeException("invalid character escape " + d, start, offset);
};
} else {
content = String.valueOf(d);
}
if (offset >= src.length() || src.charAt(offset) != '\'') {
throw new TokenizeException("unterminated character literal", start, offset);
}
offset += Character.charCount('\'');
type = Type.LIT_CHAR;
} else if (c == '"') {
offset += Character.charCount(src.charAt(offset));
StringBuilder sb = new StringBuilder();
while (offset < src.length() && src.charAt(offset) != '"') {
char d = src.charAt(offset);
offset += Character.charCount(d);
if (d == '\\') {
d = src.charAt(offset);
offset += Character.charCount(d);
char s = switch (d) {
case 'n':
yield '\n';
case 'r':
yield '\r';
case 't':
yield '\t';
case '\\':
yield '\\';
case '\'':
yield '\'';
case '"':
yield '"';
case '?':
yield '?';
default:
throw new TokenizeException("invalid string escape " + d, start, offset);
};
sb.append(s);
} else if (d == '\n') {
break;
} else {
sb.append(d);
}
}
if (offset >= src.length() || src.charAt(offset) != '"') {
throw new TokenizeException("unterminated string literal", start, offset);
}
content = sb.toString();
offset += Character.charCount('\"');
type = Type.LIT_STR;
} else {
type = null;
for (Type t : Type.PUNCT) {
if (match(src, start, t.lex)) {
type = t;
content = t.lex;
offset += t.lex.length();
break;
}
}
if (type == null) {
throw new TokenizeException("unexpected character '" + src.charAt(start) + "'", start, offset);
}
}
Objects.requireNonNull(content);
return new Token(type, content, start, offset);
}
public static class TokenizeException extends IllegalArgumentException {
public final int start, end;
public TokenizeException(String msg, int start, int end) {
super(msg);
this.start = start;
this.end = end;
}
@Override
public String toString() {
return "TokenizeException at " + start + ":" + end + ", " + getLocalizedMessage();
}
}
@Override
public boolean equals(Object o) {
if (o instanceof String s) return Objects.equals(content, s);
if (!(o instanceof Token token)) return false;
return end == token.end && start == token.start && Objects.equals(content, token.content) && type == token.type;
}
@Override
public int hashCode() {
return Objects.hash(type, content, start, end);
}
@Override
public Location getStart() {
return new Location(start);
}
@Override
public Location getEnd() {
return new Location(end);
}
@Override
public String getContent() {
return content();
}
}

View File

@@ -0,0 +1,139 @@
package mtmc.lang.sea.ast;
import mtmc.lang.Span;
import mtmc.lang.sea.Token;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public sealed abstract class Ast permits Declaration, DeclarationFunc.Param, DeclarationStruct.Field, Expression, Statement, TypeExpr, Unit {
public final Token start, end;
public Ast(Token start, Token end) {
this.start = start;
this.end = end;
}
public Span span() {
return Span.of(start, end);
}
public Stream<Ast> getChildren() {
return switch (this) {
case DeclarationFunc declarationFunc -> {
Stream<Ast> out = Stream.of(declarationFunc.returnType);
out = Stream.concat(out, declarationFunc.params.params().stream());
if (declarationFunc.body != null) {
out = Stream.concat(out, Stream.of(declarationFunc.body));
}
yield out;
}
case DeclarationSyntaxError ignored -> Stream.empty();
case DeclarationTypedef declarationTypedef -> Stream.of(declarationTypedef.type);
case DeclarationVar declarationVar -> {
Stream<Ast> out = Stream.of(declarationVar.type);
if (declarationVar.initializer != null) {
out = Stream.concat(out, Stream.of(declarationVar.initializer));
}
yield out;
}
case DeclarationStruct struct -> struct.fields.stream().map(x -> x);
case DeclarationStruct.Field field -> Stream.of(field.type);
case DeclarationFunc.Param param -> Stream.of(param.type);
case ExpressionAccess expressionAccess -> Stream.of(expressionAccess.value);
case ExpressionBin expressionBin -> Stream.of(expressionBin.lhs, expressionBin.rhs);
case ExpressionCall expressionCall -> {
Stream<Ast> out = Stream.of(expressionCall.functor);
out = Stream.concat(out, expressionCall.args.stream());
yield out;
}
case ExpressionInitializer init -> init.values.stream().map(x -> x);
case ExpressionCast expressionCast -> Stream.of(expressionCast.type, expressionCast.value);
case ExpressionChar ignored -> Stream.empty();
case ExpressionIdent ignored -> Stream.empty();
case ExpressionIndex expressionIndex -> Stream.of(expressionIndex.array, expressionIndex.index);
case ExpressionInteger ignored -> Stream.empty();
case ExpressionParens expressionParens -> Stream.of(expressionParens.inner);
case ExpressionPostfix expressionPostfix -> Stream.of(expressionPostfix.inner);
case ExpressionPrefix expressionPrefix -> Stream.of(expressionPrefix.inner);
case ExpressionString ignored -> Stream.empty();
case ExpressionTypeError typeError -> Stream.of(typeError.inner);
case ExpressionSyntaxError expressionSyntaxError -> {
if (expressionSyntaxError.child != null) {
yield Stream.of(expressionSyntaxError.child);
} else {
yield Stream.empty();
}
}
case ExpressionTernary expressionTernary -> Stream.of(
expressionTernary.cond,
expressionTernary.then,
expressionTernary.otherwise
);
case StatementBlock statementBlock -> statementBlock.statements.stream().map(x -> x);
case StatementBreak ignored -> Stream.empty();
case StatementContinue ignored -> Stream.empty();
case StatementDoWhile statementDoWhile -> Stream.of(statementDoWhile.body, statementDoWhile.condition);
case StatementExpression statementExpression -> Stream.of(statementExpression.expression);
case StatementFor statementFor -> {
Stream<Ast> out = Stream.empty();
if (statementFor.initExpression != null) {
out = Stream.concat(out, Stream.of(statementFor.initExpression));
} else if (statementFor.initStatement != null) {
out = Stream.concat(out, Stream.of(statementFor.initStatement));
}
if (statementFor.condition != null) {
out = Stream.concat(out, Stream.of(statementFor.condition));
}
if (statementFor.inc != null) {
out = Stream.concat(out, Stream.of(statementFor.inc));
}
out = Stream.concat(out, Stream.of(statementFor.body));
yield out;
}
case StatementGoto ignored -> Stream.empty();
case StatementIf statementIf -> {
Stream<Ast> out = Stream.of(statementIf.condition, statementIf.body);
if (statementIf.elseBody != null) out = Stream.concat(out, Stream.of(statementIf.elseBody));
yield out;
}
case StatementReturn statementReturn -> {
if (statementReturn.value == null) {
yield Stream.empty();
} else {
yield Stream.of(statementReturn.value);
}
}
case StatementSyntaxError ignored -> Stream.empty();
case StatementVar statementVar -> {
Stream<Ast> out = Stream.of(statementVar.type);
if (statementVar.initValue != null) {
out = Stream.concat(out, Stream.of(statementVar.initValue));
}
yield out;
}
case StatementWhile statementWhile -> Stream.of(statementWhile.condition, statementWhile.body);
case TypeExprArray typeExprArray -> Stream.of(typeExprArray.inner);
case TypeExprChar ignored -> Stream.empty();
case TypeExprInt ignored -> Stream.empty();
case TypeExprRef ignored -> Stream.empty();
case TypeExprVoid ignored -> Stream.empty();
case TypePointer ignored -> Stream.empty();
case Unit unit -> unit.declarations.stream().map(x -> x);
};
}
public List<Error> collectErrors() {
var errors = new ArrayList<Error>();
collectErrors(errors);
return errors;
}
public void collectErrors(List<Error> errors) {
if (this instanceof Error e) {
errors.add(e);
}
getChildren().forEach(child -> child.collectErrors(errors));
}
}

View File

@@ -0,0 +1,9 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.Token;
public abstract sealed class Declaration extends Ast permits DeclarationFunc, DeclarationStruct, DeclarationSyntaxError, DeclarationTypedef, DeclarationVar {
public Declaration(Token start, Token end) {
super(start, end);
}
}

View File

@@ -0,0 +1,59 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.SeaType;
import mtmc.lang.sea.Token;
import java.util.ArrayList;
import java.util.List;
public final class DeclarationFunc extends Declaration {
public final TypeExpr returnType;
public final Token name;
public final ParamList params;
public final StatementBlock body;
public DeclarationFunc(TypeExpr returnType, Token name, ParamList paramList, StatementBlock body, Token end) {
super(returnType.start, end);
this.returnType = returnType;
this.name = name;
this.params = paramList;
this.body = body;
}
public static final class Param extends Ast {
public final TypeExpr type;
public final Token name;
public Param(TypeExpr type, Token name) {
super(type.start, name.end() < type.end.end() ? type.end : name);
this.type = type;
this.name = name;
}
}
private SeaType.Func type;
public SeaType.Func type() {
if (type == null) {
var paramTypes = new ArrayList<SeaType>(params.size());
for (Param param : params.params) {
paramTypes.add(param.type.type());
}
type = new SeaType.Func(
paramTypes,
params.isVararg,
returnType.type()
);
}
return type;
}
public record ParamList(List<Param> params, boolean isVararg) {
public int size() {
return params.size();
}
public SeaType getParamType(int i) {
return params.get(i).type.type();
}
}
}

View File

@@ -0,0 +1,54 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.SeaType;
import mtmc.lang.sea.Token;
import java.util.LinkedHashMap;
import java.util.List;
public final class DeclarationStruct extends Declaration implements TypeDeclaration {
public final Token name;
public final List<Field> fields;
public DeclarationStruct(Token start, Token name, List<Field> fields, Token end) {
super(start, end);
this.name = name;
this.fields = List.copyOf(fields);
}
public String name() {
return name.content();
}
private SeaType type;
@Override
public SeaType type() {
if (type == null) {
var fields = new LinkedHashMap<String, SeaType>();
for (var field : this.fields) {
fields.put(field.name(), field.type());
}
type = new SeaType.Struct(name(), fields);
}
return type;
}
public static final class Field extends Ast {
public final TypeExpr type;
public final Token name;
public Field(TypeExpr type, Token name) {
super(type.start, name);
this.type = type;
this.name = name;
}
public String name() {
return name.content();
}
public SeaType type() {
return type.type();
}
}
}

View File

@@ -0,0 +1,18 @@
package mtmc.lang.sea.ast;
import mtmc.lang.ParseException;
import mtmc.lang.sea.Token;
public final class DeclarationSyntaxError extends Declaration implements SyntaxError {
public final ParseException exception;
public DeclarationSyntaxError(Token token, ParseException parseException) {
super(token, token);
this.exception = parseException;
}
@Override
public ParseException exception() {
return exception;
}
}

View File

@@ -0,0 +1,26 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.SeaType;
import mtmc.lang.sea.Token;
public final class DeclarationTypedef extends Declaration implements TypeDeclaration {
public final TypeExpr type;
public final Token name;
public DeclarationTypedef(Token start, TypeExpr type, Token name, Token end) {
super(start, type.end);
this.type = type;
this.name = name;
}
@Override
public String name() {
return name.content();
}
@Override
public SeaType type() {
return type.type();
}
}

View File

@@ -0,0 +1,20 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.Token;
public final class DeclarationVar extends Declaration {
public final TypeExpr type;
public final Token name;
public final Expression initializer;
public DeclarationVar(TypeExpr type, Token name, Expression initializer) {
super(type.start, initializer == null ? name : initializer.end);
this.type = type;
this.name = name;
this.initializer = initializer;
}
public String name() {
return this.name.content();
}
}

View File

@@ -0,0 +1,7 @@
package mtmc.lang.sea.ast;
import mtmc.lang.ParseException;
public interface Error {
ParseException exception();
}

View File

@@ -0,0 +1,23 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.SeaType;
import mtmc.lang.sea.Token;
import java.util.Objects;
public sealed abstract class Expression extends Ast permits ExpressionAccess, ExpressionBin, ExpressionCall, ExpressionCast, ExpressionChar, ExpressionIdent, ExpressionIndex, ExpressionInitializer, ExpressionInteger, ExpressionParens, ExpressionPostfix, ExpressionPrefix, ExpressionString, ExpressionSyntaxError, ExpressionTernary, ExpressionTypeError {
private final SeaType type;
public Expression(Token start, Token end, SeaType type) {
super(start, end);
this.type = Objects.requireNonNull(type, "'type' cannot be null");
}
public SeaType type() {
return type;
}
public enum ValueKind {
Addressable,
Immediate
}
}

View File

@@ -0,0 +1,17 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.SeaType;
import mtmc.lang.sea.Token;
public final class ExpressionAccess extends Expression {
public final Expression value;
public final Token access;
public final Token prop;
public ExpressionAccess(Expression value, Token access, Token prop, SeaType type) {
super(value.start, prop, type);
this.value = value;
this.access = access;
this.prop = prop;
}
}

View File

@@ -0,0 +1,21 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.SeaType;
import mtmc.lang.sea.Token;
public final class ExpressionBin extends Expression {
public final Expression lhs;
public final Token op;
public final Expression rhs;
public ExpressionBin(Expression lhs, Token op, Expression rhs, SeaType type) {
super(lhs.start, rhs.end, type);
this.lhs = lhs;
this.op = op;
this.rhs = rhs;
}
public String op() {
return op.content();
}
}

View File

@@ -0,0 +1,17 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.SeaType;
import mtmc.lang.sea.Token;
import java.util.List;
public final class ExpressionCall extends Expression {
public final Expression functor;
public final List<Expression> args;
public ExpressionCall(Expression functor, List<Expression> args, Token end, SeaType type) {
super(functor.start, end, type);
this.functor = functor;
this.args = args;
}
}

View File

@@ -0,0 +1,14 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.Token;
public final class ExpressionCast extends Expression {
public final TypeExpr type;
public final Expression value;
public ExpressionCast(Token start, TypeExpr type, Expression value) {
super(start, value.end, type.type());
this.type = type;
this.value = value;
}
}

View File

@@ -0,0 +1,14 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.SeaType;
import mtmc.lang.sea.Token;
public final class ExpressionChar extends Expression {
public ExpressionChar(Token token) {
super(token, token, SeaType.CHAR);
}
public Character content() {
return start.content().charAt(0);
}
}

View File

@@ -0,0 +1,17 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.SeaType;
import mtmc.lang.sea.Token;
public final class ExpressionIdent extends Expression {
public final boolean isAddressable;
public ExpressionIdent(Token token, SeaType type, boolean isAddressable) {
super(token, token, type);
this.isAddressable = isAddressable;
}
public String name() {
return start.content();
}
}

View File

@@ -0,0 +1,14 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.SeaType;
import mtmc.lang.sea.Token;
public final class ExpressionIndex extends Expression {
public final Expression array, index;
public ExpressionIndex(Expression array, Expression index, Token end, SeaType type) {
super(array.start, end, type);
this.array = array;
this.index = index;
}
}

View File

@@ -0,0 +1,27 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.SeaType;
import mtmc.lang.sea.Token;
import java.util.ArrayList;
import java.util.List;
public final class ExpressionInitializer extends Expression {
public final List<Expression> values;
private static SeaType.Initializer blobType(List<Expression> values) {
var types = new ArrayList<SeaType>();
for (var value : values) {
types.add(value.type());
}
return new SeaType.Initializer(types);
}
public ExpressionInitializer(Token start, List<Expression> values, Token end) {
super(start, end, blobType(values));
this.values = List.copyOf(values);
}
}

View File

@@ -0,0 +1,13 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.SeaType;
import mtmc.lang.sea.Token;
public final class ExpressionInteger extends Expression {
public final int value;
public ExpressionInteger(Token start) {
super(start, start, SeaType.INT);
this.value = Integer.parseInt(start.content());
}
}

View File

@@ -0,0 +1,12 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.Token;
public final class ExpressionParens extends Expression {
public final Expression inner;
public ExpressionParens(Token start, Expression inner, Token end) {
super(start, end, inner.type());
this.inner = inner;
}
}

View File

@@ -0,0 +1,17 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.SeaType;
import mtmc.lang.sea.Token;
public final class ExpressionPostfix extends Expression {
public Expression inner;
public ExpressionPostfix(Expression lhs, Token op, SeaType type) {
super(lhs.start, op, type);
this.inner = lhs;
}
public String op() {
return end.content();
}
}

View File

@@ -0,0 +1,17 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.SeaType;
import mtmc.lang.sea.Token;
public final class ExpressionPrefix extends Expression {
public final Expression inner;
public ExpressionPrefix(Token operator, Expression rhs, SeaType type) {
super(operator, rhs.end, type);
this.inner = rhs;
}
public String op() {
return start.content();
}
}

View File

@@ -0,0 +1,18 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.SeaType;
import mtmc.lang.sea.Token;
public final class ExpressionString extends Expression {
public ExpressionString(Token token) {
super(token, token, new SeaType.Pointer(SeaType.CHAR));
}
public byte[] getBytes() {
return start.content().getBytes();
}
public String content() {
return start.content();
}
}

View File

@@ -0,0 +1,27 @@
package mtmc.lang.sea.ast;
import mtmc.lang.ParseException;
import mtmc.lang.sea.SeaType;
import mtmc.lang.sea.Token;
import org.jetbrains.annotations.Nullable;
public final class ExpressionSyntaxError extends Expression implements SyntaxError {
@Nullable
public final Expression child;
public final ParseException exception;
public ExpressionSyntaxError(Token token, String message) {
this(null, token, message);
}
public ExpressionSyntaxError(@Nullable Expression child, Token token, String message) {
super(child == null ? token : child.start, token, SeaType.INT);
this.child = child;
this.exception = new ParseException(new ParseException.Message(token, message));
}
@Override
public ParseException exception() {
return exception;
}
}

View File

@@ -0,0 +1,16 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.SeaType;
public final class ExpressionTernary extends Expression {
public final Expression cond;
public final Expression then;
public final Expression otherwise;
public ExpressionTernary(Expression cond, Expression then, Expression otherwise, SeaType type) {
super(cond.start, otherwise.end, type);
this.cond = cond;
this.then = then;
this.otherwise = otherwise;
}
}

View File

@@ -0,0 +1,20 @@
package mtmc.lang.sea.ast;
import mtmc.lang.ParseException;
public final class ExpressionTypeError extends Expression implements Error {
public final Expression inner;
public final ParseException exception;
public ExpressionTypeError(Expression inner, String message) {
super(inner.start, inner.end, inner.type());
this.inner = inner;
this.exception = new ParseException(new ParseException.Message(inner.span(), message));
}
@Override
public ParseException exception() {
return exception;
}
}

View File

@@ -0,0 +1,25 @@
package mtmc.lang.sea.ast;
import mtmc.lang.ParseException;
import mtmc.lang.sea.Token;
public abstract sealed class Statement extends Ast permits StatementBlock, StatementBreak, StatementContinue, StatementDoWhile, StatementExpression, StatementFor, StatementGoto, StatementIf, StatementReturn, StatementSyntaxError, StatementVar, StatementWhile
{
private Token labelAnchor = null;
public Statement(Token start, Token end) {
super(start, end);
}
public void setLabelAnchor(Token labelAnchor) throws ParseException {
if (labelAnchor == null) return;
if (this.labelAnchor != null) {
throw new ParseException(new ParseException.Message(labelAnchor, "this statement has been labeled twice!!"));
}
this.labelAnchor = labelAnchor;
}
public Token getLabelAnchor() {
return labelAnchor;
}
}

View File

@@ -0,0 +1,14 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.Token;
import java.util.List;
public final class StatementBlock extends Statement {
public final List<Statement> statements;
public StatementBlock(Token start, List<Statement> children, Token end) {
super(start, end);
this.statements = List.copyOf(children);
}
}

View File

@@ -0,0 +1,9 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.Token;
public final class StatementBreak extends Statement {
public StatementBreak(Token breakToken) {
super(breakToken, breakToken);
}
}

View File

@@ -0,0 +1,9 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.Token;
public final class StatementContinue extends Statement {
public StatementContinue(Token continueToken) {
super(continueToken, continueToken);
}
}

View File

@@ -0,0 +1,14 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.Token;
public final class StatementDoWhile extends Statement {
public final Statement body;
public final Expression condition;
public StatementDoWhile(Token start, Statement body, Expression condition, Token end) {
super(start, end);
this.body = body;
this.condition = condition;
}
}

View File

@@ -0,0 +1,10 @@
package mtmc.lang.sea.ast;
public final class StatementExpression extends Statement {
public final Expression expression;
public StatementExpression(Expression expression) {
super(expression.start, expression.end);
this.expression = expression;
}
}

View File

@@ -0,0 +1,20 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.Token;
public final class StatementFor extends Statement {
public final Expression initExpression;
public final StatementVar initStatement;
public final Expression condition;
public final Expression inc;
public final Statement body;
public StatementFor(Token start, Expression initExpression, StatementVar initStatement, Expression condition, Expression inc, Statement body) {
super(start, body.end);
this.initExpression = initExpression;
this.initStatement = initStatement;
this.condition = condition;
this.inc = inc;
this.body = body;
}
}

View File

@@ -0,0 +1,12 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.Token;
public final class StatementGoto extends Statement {
public final Token label;
public StatementGoto(Token start, Token label) {
super(start, label);
this.label = label;
}
}

View File

@@ -0,0 +1,16 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.Token;
public final class StatementIf extends Statement {
public final Expression condition;
public final Statement body;
public final Statement elseBody;
public StatementIf(Token start, Expression condition, Statement body, Statement elseBody) {
super(start, elseBody == null ? body.end : elseBody.end);
this.condition = condition;
this.body = body;
this.elseBody = elseBody;
}
}

View File

@@ -0,0 +1,12 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.Token;
public final class StatementReturn extends Statement {
public final Expression value;
public StatementReturn(Token start, Expression value) {
super(start, value == null ? start : value.end);
this.value = value;
}
}

View File

@@ -0,0 +1,18 @@
package mtmc.lang.sea.ast;
import mtmc.lang.ParseException;
import mtmc.lang.sea.Token;
public final class StatementSyntaxError extends Statement implements SyntaxError {
public final ParseException exception;
public StatementSyntaxError(Token token, ParseException exception) {
super(token, token);
this.exception = exception;
}
@Override
public ParseException exception() {
return exception;
}
}

View File

@@ -0,0 +1,20 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.Token;
public final class StatementVar extends Statement {
public final TypeExpr type;
public final Token name;
public final Expression initValue;
public StatementVar(TypeExpr type, Token name, Expression initValue) {
super(type.start, initValue == null ? name : initValue.end);
this.type = type;
this.name = name;
this.initValue = initValue;
}
public String name() {
return name.content();
}
}

View File

@@ -0,0 +1,14 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.Token;
public final class StatementWhile extends Statement {
public final Expression condition;
public final Statement body;
public StatementWhile(Token start, Expression condition, Statement body) {
super(start, body.end);
this.condition = condition;
this.body = body;
}
}

View File

@@ -0,0 +1,7 @@
package mtmc.lang.sea.ast;
import mtmc.lang.ParseException;
public interface SyntaxError extends Error {
ParseException exception();
}

View File

@@ -0,0 +1,8 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.SeaType;
public interface TypeDeclaration {
String name();
SeaType type();
}

View File

@@ -0,0 +1,19 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.SeaType;
import mtmc.lang.sea.Token;
import java.util.Objects;
public abstract sealed class TypeExpr extends Ast permits TypeExprArray, TypeExprChar, TypeExprInt, TypeExprRef, TypeExprVoid, TypePointer {
private final SeaType type;
public TypeExpr(Token start, Token end, SeaType type) {
super(start, end);
this.type = Objects.requireNonNull(type);
}
public SeaType type() {
return type;
}
}

View File

@@ -0,0 +1,13 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.SeaType;
import mtmc.lang.sea.Token;
public final class TypeExprArray extends TypeExpr {
public final TypeExpr inner;
public TypeExprArray(TypeExpr inner, Token end) {
super(inner.start, end, new SeaType.Pointer(inner.type()));
this.inner = inner;
}
}

View File

@@ -0,0 +1,13 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.SeaType;
import mtmc.lang.sea.Token;
public final class TypeExprChar extends TypeExpr {
public final Token token;
public TypeExprChar(Token token) {
super(token, token, SeaType.CHAR);
this.token = token;
}
}

View File

@@ -0,0 +1,10 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.SeaType;
import mtmc.lang.sea.Token;
public final class TypeExprInt extends TypeExpr {
public TypeExprInt(Token token) {
super(token, token, SeaType.INT);
}
}

View File

@@ -0,0 +1,12 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.Token;
public final class TypeExprRef extends TypeExpr {
public final TypeDeclaration decl;
public TypeExprRef(Token name, TypeDeclaration decl) {
super(name, name, decl.type());
this.decl = decl;
}
}

View File

@@ -0,0 +1,10 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.SeaType;
import mtmc.lang.sea.Token;
public final class TypeExprVoid extends TypeExpr {
public TypeExprVoid(Token token) {
super(token, token, SeaType.VOID);
}
}

View File

@@ -0,0 +1,13 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.SeaType;
import mtmc.lang.sea.Token;
public final class TypePointer extends TypeExpr {
public final TypeExpr component;
public TypePointer(TypeExpr component, Token star) {
super(component.start, star, new SeaType.Pointer(component.type()));
this.component = component;
}
}

View File

@@ -0,0 +1,22 @@
package mtmc.lang.sea.ast;
import mtmc.lang.sea.Symbol;
import mtmc.lang.sea.Token;
import java.util.LinkedHashMap;
import java.util.List;
public final class Unit extends Ast {
public final String source;
public final String filename;
public final List<Declaration> declarations;
public final LinkedHashMap<String, Symbol> symbols;
public Unit(String filename, String source, List<Declaration> declarations, LinkedHashMap<String, Symbol> globals) {
super(Token.SOF, Token.EOF);
this.source = source;
this.filename = filename;
this.declarations = declarations;
this.symbols = globals;
}
}

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