6502Android/app/src/main/kotlin/android/emu6502/Assembler.kt

426 lines
11 KiB
Kotlin

package android.emu6502
import android.emu6502.instructions.Instruction
import android.emu6502.instructions.Opcodes
import android.emu6502.instructions.Symbols
import java.util.regex.Pattern
class Assembler(private var memory: Memory, private val symbols: Symbols) {
var codeLen = 0
var defaultCodePC = BOOTSTRAP_ADDRESS
private var labels = Labels(this, symbols)
fun assembleCode(lines: List<String>) {
val preprocessedLines = preprocess(lines)
labels.indexLines(preprocessedLines)
// Reset PC and code length since labels screwed it
defaultCodePC = BOOTSTRAP_ADDRESS
codeLen = 0
preprocessedLines.forEachIndexed { i, line ->
if (!assembleLine(line)) {
throw RuntimeException("**Syntax error line " + (i + 1) + ": " + line + "**")
}
}
// set a null byte at the end of the code
memory.set(defaultCodePC, 0x00)
}
private fun preprocess(lines: List<String>): List<String> {
val pattern = Pattern.compile("^define\\s+(\\w+)\\s+(\\S+)", Pattern.CASE_INSENSITIVE)
val sanitizedLines = lines.map { sanitize(it) }
sanitizedLines
.map { pattern.matcher(it) }
.filter { it.find() }
.forEach { symbols[it.group(1)] = sanitize(it.group(2)) }
return sanitizedLines.filterNot { pattern.matcher(it).find() }
}
private fun sanitize(line: String): String {
return line.replace("^(.*?);.*".toRegex(), "$1").trim()
}
fun assembleLine(line: String): Boolean {
var input = line
var command: String
var param: String
val addr: Int
// Find command or label
if (input.matches("^\\w+:".toRegex())) {
if (line.matches("^\\w+:[\\s]*\\w+.*$".toRegex())) {
input = input.replace("^\\w+:[\\s]*(.*)$".toRegex(), "$1")
command = input.replace("^(\\w+).*$".toRegex(), "$1")
} else {
command = ""
}
} else {
command = input.replace("^(\\w+).*$".toRegex(), "$1")
}
// Nothing to do for blank lines
if (command == "") {
return true
}
command = command.toUpperCase()
if (input.matches("^\\*\\s*=\\s*\\$?[0-9a-f]*$".toRegex())) {
// equ spotted
param = input.replace("^\\s*\\*\\s*=\\s*".toRegex(), "")
if (param[0] == '$') {
param = param.replace("^\\$".toRegex(), "")
addr = Integer.parseInt(param, 16)
} else {
addr = Integer.parseInt(param, 10)
}
if (addr < 0 || addr > 0xffff) {
throw IllegalStateException("Unable to relocate code outside 64k memory")
}
defaultCodePC = addr
return true
}
param = if (input.matches("^\\w+\\s+.*?$".toRegex())) {
input.replace("^\\w+\\s+(.*?)".toRegex(), "$1")
} else if (input.matches("^\\w+$".toRegex())) {
""
} else {
return false
}
param = param.replace("[ ]".toRegex(), "")
if (command == "DCB") {
return DCB(param)
}
val opcode = Opcodes.MAP[Instruction.valueOf(command)]
if (opcode != null) {
if (checkImmediate(param, opcode[0].opcode)) {
return true
}
if (checkZeroPage(param, opcode[1].opcode)) {
return true
}
if (checkZeroPageX(param, opcode[2].opcode)) {
return true
}
if (checkZeroPageY(param, opcode[3].opcode)) {
return true
}
if (checkAbsolute(param, opcode[4].opcode)) {
return true
}
if (checkAbsoluteX(param, opcode[5].opcode)) {
return true
}
if (checkAbsoluteY(param, opcode[6].opcode)) {
return true
}
if (checkIndirect(param, opcode[7].opcode)) {
return true
}
if (checkIndirectX(param, opcode[8].opcode)) {
return true
}
if (checkIndirectY(param, opcode[9].opcode)) {
return true
}
if (checkSingle(param, opcode[10].opcode)) {
return true
}
if (checkBranch(param, opcode[11].opcode)) {
return true
}
}
return false
}
fun hexdump(): String {
return memory.format(0x600, codeLen)
}
private fun DCB(param: String): Boolean {
throw UnsupportedOperationException(
"not implemented") //To change body of created functions use File | Settings | File Templates.
}
/** Common branch function for all branches (BCC, BCS, BEQ, BNE..) */
private fun checkBranch(param: String, opcode: Int): Boolean {
if (opcode == 0xff) {
return false
}
var addr = -1
if (param.matches("\\w+".toRegex())) {
addr = labels.get(param)
}
if (addr == -1) {
pushWord(0x00)
return false
}
pushByte(opcode)
if (addr < (defaultCodePC - 0x600)) {
// Backwards?
pushByte((0xff - ((defaultCodePC - 0x600) - addr)).and(0xff))
return true
}
pushByte((addr - (defaultCodePC - 0x600) - 1).and(0xff))
return true
}
private fun checkAbsolute(param: String, opcode: Int): Boolean {
if (opcode == 0xff) {
return false
}
if (checkWordOperand(param, opcode, "^([\\w\\$]+)$")) {
return true
}
// it could be a label too..
return checkLabel(param, opcode, "^\\w+$".toRegex())
}
private fun checkWordOperand(param: String, opcode: Int, regex: String): Boolean {
val matcher = Pattern.compile(regex, Pattern.CASE_INSENSITIVE).matcher(param)
if (!matcher.find()) {
return false
}
return innerCheckWordOperand(matcher.group(1), opcode)
}
private fun innerCheckWordOperand(param: String, opcode: Int): Boolean {
val operand = tryParseWordOperand(param)
if (operand < 0) {
return false
}
pushByte(opcode)
pushWord(operand)
return true
}
private fun checkByteOperand(param: String, opcode: Int, regex: String): Boolean {
val matcher = Pattern.compile(regex, Pattern.CASE_INSENSITIVE).matcher(param)
if (!matcher.find()) {
return false
}
return innerCheckByteOperand(matcher.group(1), opcode)
}
private fun innerCheckByteOperand(param: String, opcode: Int): Boolean {
val operand = tryParseByteOperand(param)
if (operand < 0) {
return false
}
pushByte(opcode)
pushByte(operand)
return true
}
private fun checkLabel(param: String, opcode: Int, regex: Regex): Boolean {
if (param.matches(regex)) {
val finalParam = param.replace(",Y", "", true).replace(",X", "", true)
pushByte(opcode)
val addr = labels[finalParam]
if (addr != -1) {
if (addr < 0 || addr > 0xffff) {
return false
}
pushWord(addr)
return true
} else {
pushWord(0xffff) // filler, only used while indexing labels
return true
}
}
return false
}
private fun checkIndirectY(param: String, opcode: Int): Boolean {
if (opcode == 0xff) {
return false
}
return checkByteOperand(param, opcode, "^\\(([\\w\\$]+)\\),Y$")
}
private fun checkIndirectX(param: String, opcode: Int): Boolean {
if (opcode == 0xff) {
return false
}
return checkByteOperand(param, opcode, "^\\(([\\w\\$]+),X\\)$")
}
private fun checkIndirect(param: String, opcode: Int): Boolean {
if (opcode == 0xff) {
return false
}
return checkWordOperand(param, opcode, "^\\(([\\w\\$]+)\\)$")
}
private fun checkAbsoluteY(param: String, opcode: Int): Boolean {
if (opcode == 0xff) {
return false
}
return checkWordOperand(param, opcode, "^([\\w\\$]+),Y$") ||
checkLabel(param, opcode, "^\\w+,Y$".toRegex())
}
private fun checkAbsoluteX(param: String, opcode: Int): Boolean {
if (opcode == 0xff) {
return false
}
return checkWordOperand(param, opcode, "^([\\w\\$]+),X$") ||
checkLabel(param, opcode, "^\\w+,X$".toRegex())
}
private fun checkZeroPageY(param: String, opcode: Int): Boolean {
if (opcode == 0xff) {
return false
}
return checkByteOperand(param, opcode, "^([\\w\\$]+),Y")
}
private fun checkZeroPageX(param: String, opcode: Int): Boolean {
if (opcode == 0xff) {
return false
}
return checkByteOperand(param, opcode, "^([\\w\\$]+),X")
}
private fun checkZeroPage(param: String, opcode: Int): Boolean {
if (opcode == 0xff) {
return false
}
return innerCheckByteOperand(param, opcode)
}
private fun checkImmediate(param: String, opcode: Int): Boolean {
if (opcode == 0xff) {
return false
}
if (checkByteOperand(param, opcode, "^#([\\w\\$]+)$")) {
return true
}
// Label lo/hi
if (param.matches("^#[<>]\\w+$".toRegex())) {
val label = param.replace("^#[<>](\\w+)$".toRegex(), "$1")
val hilo = param.replace("^#([<>]).*$".toRegex(), "$1")
pushByte(opcode)
val addr = labels[label]
if (addr != -1) {
when (hilo) {
">" -> {
pushByte(addr.shr(8).and(0xFF))
}
"<" -> {
pushByte(addr.and(0xFF))
}
else -> return false
}
} else {
pushByte(0x00)
return true
}
}
return false
}
// Try to parse the given parameter as a byte operand.
// Returns the (positive) value if successful, otherwise -1
private fun tryParseByteOperand(param: String): Int {
var value: Int = -1
var parameter = param
if (parameter.matches("^\\w+$".toRegex())) {
val lookupVal = symbols[parameter] // Substitute symbol by actual value, then proceed
if (lookupVal != null) {
parameter = lookupVal
}
}
// Is it a hexadecimal operand?
var pattern = Pattern.compile("^\\$([0-9a-f]{1,2})$", Pattern.CASE_INSENSITIVE)
var matcher = pattern.matcher(parameter)
if (matcher.find()) {
value = Integer.parseInt(matcher.group(1), 16)
} else {
// Is it a decimal operand?
pattern = Pattern.compile("^([0-9]{1,3})$", Pattern.CASE_INSENSITIVE)
matcher = pattern.matcher(parameter)
if (matcher.find()) {
value = Integer.parseInt(matcher.group(1), 10)
}
}
// Validate range
if (value in 0..0xff) {
return value
}
return -1
}
private fun tryParseWordOperand(param: String): Int {
var value: Int = -1
var parameter = param
if (parameter.matches("^\\w+$".toRegex())) {
val lookupVal = symbols[parameter] // Substitute symbol by actual value, then proceed
if (lookupVal != null) {
parameter = lookupVal
}
}
// Is it a hexadecimal operand?
var pattern = Pattern.compile("^\\$([0-9a-f]{3,4})$", Pattern.CASE_INSENSITIVE)
var matcher = pattern.matcher(parameter)
if (matcher.find()) {
value = Integer.parseInt(matcher.group(1), 16)
} else {
// Is it a decimal operand?
pattern = Pattern.compile("^([0-9]{1,5})$", Pattern.CASE_INSENSITIVE)
matcher = pattern.matcher(parameter)
if (matcher.find()) {
value = Integer.parseInt(matcher.group(1), 10)
}
}
// Validate range
if (value in 0..0xffff) {
return value
}
return -1
}
private fun pushByte(value: Int) {
memory.set(defaultCodePC, value.and(0xff))
defaultCodePC++
codeLen++
}
private fun pushWord(value: Int) {
pushByte(value.and(0xff))
pushByte(value.shr(8).and(0xff))
}
private fun checkSingle(param: String, opcode: Int): Boolean {
// Accumulator instructions are counted as single-byte opcodes
if (param != "" && param != "A") {
return false
}
pushByte(opcode)
return true
}
companion object {
const val BOOTSTRAP_ADDRESS = 0x600
}
}