mirror of https://github.com/irmen/ksim65.git
238 lines
13 KiB
Kotlin
238 lines
13 KiB
Kotlin
package razorvine.ksim65
|
|
|
|
import razorvine.ksim65.components.Address
|
|
import razorvine.ksim65.components.MemMappedComponent
|
|
|
|
|
|
class Assembler(cpu: Cpu6502, val memory: MemMappedComponent, initialStartAddress: Address? = null) {
|
|
|
|
companion object {
|
|
fun parseRelativeToPC(relative: String, currentAddress: Int): Int {
|
|
val rest = relative.substring(1).trim()
|
|
if(rest.isNotEmpty()) {
|
|
return when(rest[0]) {
|
|
'-' -> currentAddress-parseNumber(rest.substring(1))
|
|
'+' -> currentAddress+parseNumber(rest.substring(1))
|
|
else -> throw NumberFormatException("invalid address syntax")
|
|
}
|
|
}
|
|
return currentAddress
|
|
}
|
|
|
|
fun parseNumber(number: String, decimalFirst: Boolean = false): Int {
|
|
val num = number.trim()
|
|
if (num.isBlank()) return 0
|
|
if (decimalFirst && num[0].isDigit()) return num.toInt(10)
|
|
return when (num[0]) {
|
|
'$' -> num.substring(1).trimStart().toInt(16)
|
|
'#' -> num.substring(1).trimStart().toInt(10)
|
|
'%' -> num.substring(1).trimStart().toInt(2)
|
|
else -> num.toInt(16)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
private var startAddress = initialStartAddress ?: 0
|
|
private var assembledSize = 0
|
|
|
|
private val instructions by lazy {
|
|
val instr = cpu.instructions.withIndex().associate {
|
|
Pair(it.value.mnemonic, it.value.mode) to it.index
|
|
}.toMutableMap()
|
|
instr[Pair("nop", Cpu6502.AddrMode.Imp)] = 0xea
|
|
instr.toMap()
|
|
}
|
|
|
|
class Result(val success: Boolean, val error: String, val startAddress: Address, val numBytes: Int)
|
|
|
|
fun assemble(lines: Iterable<String>): Result {
|
|
for(line in lines) {
|
|
val result = assemble(line)
|
|
if(!result.success)
|
|
return result
|
|
assembledSize += result.numBytes
|
|
}
|
|
return Result(true, "", startAddress, assembledSize)
|
|
}
|
|
|
|
fun assemble(line: String): Result {
|
|
|
|
/*
|
|
The command is a line of the form:
|
|
|
|
"<address> <instruction> [<arguments>]"
|
|
" <instruction> [<arguments>]"
|
|
"* = <address>"
|
|
*/
|
|
|
|
var args = line.trim().split(' ')
|
|
if(args.isEmpty() || args.size == 1 && args[0] == "")
|
|
return Result(true, "", startAddress, 0)
|
|
if(args[0].startsWith("*=") && args.size==1) {
|
|
startAddress = parseNumber(args[0].substring(2))
|
|
return Result(true, "", startAddress, 0)
|
|
}
|
|
else if(args[0] == "*" && args[1] == "=") {
|
|
startAddress = parseNumber(args[2])
|
|
return Result(true, "", startAddress, 0)
|
|
} else {
|
|
// line with an instruction, may be preceded by a 4 or 5 char address
|
|
if(args[0].length == 4 || args[0].length==5) {
|
|
if(args.size!=2 && args.size !=3)
|
|
return Result(false, "syntax error", startAddress, 0)
|
|
startAddress = parseNumber(args[0])
|
|
args = args.drop(1)
|
|
}
|
|
}
|
|
|
|
val instructionSize: Int
|
|
val mnemonic = args[0].lowercase().trim()
|
|
when (args.size) {
|
|
1 -> {
|
|
// implied or acc
|
|
instructionSize = 1
|
|
var instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.Imp)]
|
|
if (instruction == null) instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.Acc)]
|
|
if (instruction == null) return Result(false, "invalid instruction", this.startAddress, 0)
|
|
memory[startAddress+assembledSize] = instruction.toShort()
|
|
}
|
|
2 -> {
|
|
val arg = args[1]
|
|
when {
|
|
arg.startsWith('#') -> {
|
|
// immediate
|
|
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.Imm)] ?: return Result(false, "invalid instruction",
|
|
this.startAddress, 0)
|
|
memory[startAddress+assembledSize] = instruction.toShort()
|
|
memory[startAddress+assembledSize+1] = parseNumber(arg.substring(1), decimalFirst = true).toShort()
|
|
instructionSize = 2
|
|
}
|
|
arg.startsWith("(") && arg.endsWith(",x)") -> {
|
|
// indirect X
|
|
val indAddress = try {
|
|
parseNumber(arg.substring(1, arg.length-3))
|
|
} catch (x: NumberFormatException) {
|
|
return Result(false, "invalid instruction", this.startAddress, 0)
|
|
}
|
|
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.IzX)] ?: return Result(false, "invalid instruction",
|
|
this.startAddress, 0)
|
|
memory[startAddress+assembledSize] = instruction.toShort()
|
|
memory[startAddress+assembledSize+1] = indAddress.toShort()
|
|
instructionSize = 2
|
|
}
|
|
arg.startsWith("(") && arg.endsWith("),y") -> {
|
|
// indirect Y
|
|
val indAddress = try {
|
|
parseNumber(arg.substring(1, arg.length-3))
|
|
} catch (x: NumberFormatException) {
|
|
return Result(false, "invalid instruction", this.startAddress, 0)
|
|
}
|
|
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.IzY)] ?: return Result(false, "invalid instruction",
|
|
this.startAddress, 0)
|
|
memory[startAddress+assembledSize] = instruction.toShort()
|
|
memory[startAddress+assembledSize+1] = indAddress.toShort()
|
|
instructionSize = 2
|
|
}
|
|
arg.endsWith(",x") -> {
|
|
// indexed X or zpIndexed X
|
|
val indAddress = try {
|
|
parseNumber(arg.substring(1, arg.length-2))
|
|
} catch (x: NumberFormatException) {
|
|
return Result(false, "invalid instruction", this.startAddress, 0)
|
|
}
|
|
instructionSize = if (indAddress <= 255) {
|
|
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.ZpX)] ?: return Result(false, "invalid instruction",
|
|
this.startAddress, 0)
|
|
memory[startAddress+assembledSize] = instruction.toShort()
|
|
memory[startAddress+assembledSize+1] = indAddress.toShort()
|
|
2
|
|
} else {
|
|
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.AbsX)] ?: return Result(false, "invalid instruction",
|
|
this.startAddress, 0)
|
|
memory[startAddress+assembledSize] = instruction.toShort()
|
|
memory[startAddress+assembledSize+1] = (indAddress and 255).toShort()
|
|
memory[startAddress+assembledSize+2] = (indAddress ushr 8).toShort()
|
|
3
|
|
}
|
|
}
|
|
arg.endsWith(",y") -> {
|
|
// indexed Y or zpIndexed Y
|
|
val indAddress = try {
|
|
parseNumber(arg.substring(1, arg.length-2))
|
|
} catch (x: NumberFormatException) {
|
|
return Result(false, "invalid instruction", this.startAddress, 0)
|
|
}
|
|
instructionSize = if (indAddress <= 255) {
|
|
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.ZpY)] ?: return Result(false, "invalid instruction",
|
|
this.startAddress, 0)
|
|
memory[startAddress+assembledSize] = instruction.toShort()
|
|
memory[startAddress+assembledSize+1] = indAddress.toShort()
|
|
2
|
|
} else {
|
|
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.AbsY)] ?: return Result(false, "invalid instruction",
|
|
this.startAddress, 0)
|
|
memory[startAddress+assembledSize] = instruction.toShort()
|
|
memory[startAddress+assembledSize+1] = (indAddress and 255).toShort()
|
|
memory[startAddress+assembledSize+2] = (indAddress ushr 8).toShort()
|
|
3
|
|
}
|
|
}
|
|
arg.endsWith(")") -> {
|
|
// indirect (jmp)
|
|
val indAddress = try {
|
|
parseNumber(arg.substring(1, arg.length-1))
|
|
} catch (x: NumberFormatException) {
|
|
return Result(false, "invalid instruction", this.startAddress, 0)
|
|
}
|
|
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.Ind)]
|
|
?: return Result(false, "invalid instruction", this.startAddress, 0)
|
|
memory[startAddress+assembledSize] = instruction.toShort()
|
|
memory[startAddress+assembledSize+1] = (indAddress and 255).toShort()
|
|
memory[startAddress+assembledSize+2] = (indAddress ushr 8).toShort()
|
|
instructionSize = 3
|
|
}
|
|
else -> {
|
|
val instr = instructions[Pair(mnemonic, Cpu6502.AddrMode.Rel)]
|
|
if (instr != null) {
|
|
// relative address
|
|
val rel = try {
|
|
parseRelativeToPC(arg, startAddress)
|
|
} catch (x: NumberFormatException) {
|
|
return Result(false, "invalid numeral", this.startAddress, 0)
|
|
}
|
|
memory[startAddress+assembledSize] = instr.toShort()
|
|
memory[startAddress+assembledSize+1] = (rel-startAddress-2 and 255).toShort()
|
|
instructionSize = 2
|
|
} else {
|
|
// absolute or absZp
|
|
val absAddress = try {
|
|
if(arg.startsWith('*')) parseRelativeToPC(arg, startAddress) else parseNumber(arg)
|
|
} catch (x: NumberFormatException) {
|
|
return Result(false, "invalid numeral", this.startAddress, 0)
|
|
}
|
|
val zpInstruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.Zp)]
|
|
instructionSize = if (absAddress <= 255 && zpInstruction != null) {
|
|
memory[startAddress+assembledSize] = zpInstruction.toShort()
|
|
memory[startAddress+assembledSize+1] = absAddress.toShort()
|
|
2
|
|
} else {
|
|
val absInstr = instructions[Pair(mnemonic, Cpu6502.AddrMode.Abs)] ?: return Result(false, "invalid instruction",
|
|
this.startAddress, 0)
|
|
memory[startAddress+assembledSize] = absInstr.toShort()
|
|
memory[startAddress+assembledSize+1] = (absAddress and 255).toShort()
|
|
memory[startAddress+assembledSize+2] = (absAddress ushr 8).toShort()
|
|
3
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else ->
|
|
return Result(false, "syntax error", this.startAddress, 0)
|
|
}
|
|
|
|
return Result(true, "", this.startAddress, instructionSize)
|
|
}
|
|
}
|