From a7769ba7f070e2777b6b815836a8dc64d3a54620 Mon Sep 17 00:00:00 2001 From: Felipe Lima Date: Sun, 14 Jun 2015 22:51:31 -0700 Subject: [PATCH] Fixes labels, adds more tests --- .../main/kotlin/android/emu6502/Assembler.kt | 80 +++++++++++++++---- .../main/kotlin/android/emu6502/Emulator.kt | 4 +- app/src/main/kotlin/android/emu6502/Labels.kt | 40 ++++++++-- .../main/kotlin/android/emu6502/Symbols.kt | 13 --- .../android/emu6502/instructions/Symbols.kt | 6 ++ .../java/android/emu6502/AssemblerTest.java | 78 +++++++++++++++++- .../test/java/android/emu6502/LabelsTest.java | 28 +++++++ 7 files changed, 211 insertions(+), 38 deletions(-) delete mode 100644 app/src/main/kotlin/android/emu6502/Symbols.kt create mode 100644 app/src/main/kotlin/android/emu6502/instructions/Symbols.kt create mode 100644 app/src/test/java/android/emu6502/LabelsTest.java diff --git a/app/src/main/kotlin/android/emu6502/Assembler.kt b/app/src/main/kotlin/android/emu6502/Assembler.kt index 8e4b9d6..bf5b741 100644 --- a/app/src/main/kotlin/android/emu6502/Assembler.kt +++ b/app/src/main/kotlin/android/emu6502/Assembler.kt @@ -2,18 +2,28 @@ package android.emu6502 import android.emu6502.instructions.Instruction import android.emu6502.instructions.Opcodes +import android.emu6502.instructions.Symbols +import java.util.HashMap import java.util.regex.Pattern import kotlin.text.Regex -class Assembler(private var labels: Labels, private var memory: Memory, - private var symbols: Symbols) { +class Assembler(private var memory: Memory, private val symbols: Symbols) { var codeLen = 0 val BOOTSTRAP_ADDRESS = 0x600 var defaultCodePC = BOOTSTRAP_ADDRESS + private var labels = Labels(this, symbols) fun assembleCode(lines: List) { - lines.forEachIndexed { i, line -> + 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 + "**") } @@ -22,7 +32,24 @@ class Assembler(private var labels: Labels, private var memory: Memory, memory.set(defaultCodePC, 0x00) } - private fun assembleLine(line: String): Boolean { + private fun preprocess(lines: List): List { + val pattern = Pattern.compile("^define\\s+(\\w+)\\s+(\\S+)", Pattern.CASE_INSENSITIVE) + + var sanitizedLines = lines.map { sanitize(it) } + + sanitizedLines + .map { pattern.matcher(it) } + .filter { it.find() } + .forEach { symbols.put(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 @@ -56,7 +83,7 @@ class Assembler(private var labels: Labels, private var memory: Memory, } else { addr = Integer.parseInt(param, 10) } - if ((addr < 0) || (addr > 0xffff)) { + if (addr < 0 || addr > 0xffff) { throw IllegalStateException("Unable to relocate code outside 64k memory") } defaultCodePC = addr @@ -73,7 +100,7 @@ class Assembler(private var labels: Labels, private var memory: Memory, param = param.replace("[ ]".toRegex(), "") - if (command === "DCB") { + if (command == "DCB") { return DCB(param) } @@ -129,12 +156,35 @@ class Assembler(private var labels: Labels, private var memory: Memory, "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 { - throw UnsupportedOperationException( - "not implemented") //To change body of created functions use File | Settings | File Templates. + 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 } @@ -183,8 +233,8 @@ class Assembler(private var labels: Labels, private var memory: Memory, if (param.matches(regex)) { val finalParam = param.replace(",Y", "", true).replace(",X", "", true) pushByte(opcode) - if (labels.find(finalParam)) { - val addr = (labels.getPC(finalParam)) + val addr = labels.get(finalParam) + if (addr != null) { if (addr < 0 || addr > 0xffff) { return false } @@ -269,8 +319,8 @@ class Assembler(private var labels: Labels, private var memory: Memory, var label = param.replace("^#[<>](\\w+)$".toRegex(), "$1") var hilo = param.replace("^#([<>]).*$".toRegex(), "$1") pushByte(opcode) - if (labels.find(label)) { - var addr = labels.getPC(label) + val addr = labels.get(label) + if (addr != null) { when (hilo) { ">" -> { pushByte(addr.shr(8).and(0xFF)) @@ -296,7 +346,7 @@ class Assembler(private var labels: Labels, private var memory: Memory, var parameter = param if (parameter.matches("^\\w+$".toRegex())) { - var lookupVal = symbols.lookup(parameter) // Substitute symbol by actual value, then proceed + var lookupVal = symbols.get(parameter) // Substitute symbol by actual value, then proceed if (lookupVal != null) { parameter = lookupVal } @@ -328,7 +378,7 @@ class Assembler(private var labels: Labels, private var memory: Memory, var parameter = param if (parameter.matches("^\\w+$".toRegex())) { - var lookupVal = symbols.lookup(parameter) // Substitute symbol by actual value, then proceed + var lookupVal = symbols.get(parameter) // Substitute symbol by actual value, then proceed if (lookupVal != null) { parameter = lookupVal } @@ -368,7 +418,7 @@ class Assembler(private var labels: Labels, private var memory: Memory, private fun checkSingle(param: String, opcode: Int): Boolean { // Accumulator instructions are counted as single-byte opcodes - if (!param.equals("") && !param.equals("A")) { + if (!param.equals("") && !param.equals("A")) { return false } pushByte(opcode) diff --git a/app/src/main/kotlin/android/emu6502/Emulator.kt b/app/src/main/kotlin/android/emu6502/Emulator.kt index 5982834..87941f2 100644 --- a/app/src/main/kotlin/android/emu6502/Emulator.kt +++ b/app/src/main/kotlin/android/emu6502/Emulator.kt @@ -1,8 +1,10 @@ package android.emu6502 +import android.emu6502.instructions.Symbols + final class Emulator { val display = Display() val memory = Memory(display) val cpu = CPU(memory) - val assembler = Assembler(Labels(), memory, Symbols()) + val assembler = Assembler(memory, Symbols()) } \ No newline at end of file diff --git a/app/src/main/kotlin/android/emu6502/Labels.kt b/app/src/main/kotlin/android/emu6502/Labels.kt index ecfe75b..508f19f 100644 --- a/app/src/main/kotlin/android/emu6502/Labels.kt +++ b/app/src/main/kotlin/android/emu6502/Labels.kt @@ -1,13 +1,39 @@ package android.emu6502 -class Labels { - fun getPC(label: String): Int { - throw UnsupportedOperationException( - "not implemented") //To change body of created functions use File | Settings | File Templates. +import android.emu6502.instructions.Symbols +import java.util.* + +class Labels(private val assembler: Assembler, private val symbols: Symbols) : HashMap() { + private val labelIndex: ArrayList = ArrayList() + + fun indexLines(lines: List) { + lines.forEach { line -> + indexLine(line) + } } - fun find(label: String): Boolean { - throw UnsupportedOperationException( - "not implemented") //To change body of created functions use File | Settings | File Templates. + fun get(label: String): Int { + return super.get(label) ?: -1 + } + + // Extract label if line contains one and calculate position in memory. + // Return false if label already exists. + private fun indexLine(input: String) { + // Figure out how many bytes this instruction takes + val currentPC = assembler.defaultCodePC; + // TODO: find a better way for Labels to have access to assembler + assembler.assembleLine(input); + + // Find command or label + if (input.matches("^\\w+:".toRegex())) { + val label = input.replace("(^\\w+):.*$".toRegex(), "$1"); + + if (symbols.get(label) != null) { + throw RuntimeException( + "**Label " + label + "is already used as a symbol; please rename one of them**"); + } + + put(label, currentPC); + } } } \ No newline at end of file diff --git a/app/src/main/kotlin/android/emu6502/Symbols.kt b/app/src/main/kotlin/android/emu6502/Symbols.kt deleted file mode 100644 index bc84ce1..0000000 --- a/app/src/main/kotlin/android/emu6502/Symbols.kt +++ /dev/null @@ -1,13 +0,0 @@ -package android.emu6502 - -class Symbols { - private val table: Map = emptyMap() - - fun lookup(key: String): String? { - return null - } - - fun add(key: String, value: String) { - - } -} diff --git a/app/src/main/kotlin/android/emu6502/instructions/Symbols.kt b/app/src/main/kotlin/android/emu6502/instructions/Symbols.kt new file mode 100644 index 0000000..de1bb4a --- /dev/null +++ b/app/src/main/kotlin/android/emu6502/instructions/Symbols.kt @@ -0,0 +1,6 @@ +package android.emu6502.instructions + +import java.util.* + +class Symbols : HashMap() { +} diff --git a/app/src/test/java/android/emu6502/AssemblerTest.java b/app/src/test/java/android/emu6502/AssemblerTest.java index 5cd8358..a50266c 100644 --- a/app/src/test/java/android/emu6502/AssemblerTest.java +++ b/app/src/test/java/android/emu6502/AssemblerTest.java @@ -2,6 +2,9 @@ package android.emu6502; import com.google.common.collect.ImmutableList; +import android.emu6502.instructions.Symbols; + +import org.junit.Before; import org.junit.Test; import java.util.List; @@ -11,7 +14,13 @@ import static org.junit.Assert.assertThat; public class AssemblerTest { - @Test public void testAssembler() { + private Assembler assembler; + + @Before public void setUp() { + assembler = new Assembler(new Memory(new Display()), new Symbols()); + } + + @Test public void testSimple() { List lines = ImmutableList.of( "LDA #$01", "STA $0200", @@ -19,8 +28,73 @@ public class AssemblerTest { "STA $0201", "LDA #$08", "STA $0202"); - Assembler assembler = new Assembler(new Labels(), new Memory(new Display()), new Symbols()); assembler.assembleCode(lines); assertThat(assembler.hexdump(), equalTo("0600: A9 01 8D 00 02 A9 05 8D 01 02 A9 08 8D 02 02")); } + + @Test public void testWithComments() { + List lines = ImmutableList.of( + "LDA #$c0 ;Load the hex value $c0 into the A register", + "TAX ;Transfer the value in the A register to X", + "INX ;Increment the value in the X register", + "ADC #$c4 ;Add the hex value $c4 to the A register", + "BRK ;Break - we're done"); + assembler.assembleCode(lines); + assertThat(assembler.hexdump(), equalTo("0600: A9 C0 AA E8 69 C4 00")); + } + + @Test public void testBranchAndLabel() { + List lines = ImmutableList.of( + "LDX #$08", + "decrement:", + "DEX", + "STX $0200", + "CPX #$03", + "BNE decrement", + "STX $0201", + "BRK"); + assembler.assembleCode(lines); + assertThat(assembler.hexdump(), equalTo("0600: A2 08 CA 8E 00 02 E0 03 D0 F8 8E 01 02 00")); + } + + @Test public void testRelative() { + List lines = ImmutableList.of( + "LDA #$01", + "CMP #$02", + "BNE notequal", + "STA $22", + "notequal:", + "BRK"); + + assembler.assembleCode(lines); + assertThat(assembler.hexdump(), equalTo("0600: A9 01 C9 02 D0 02 85 22 00")); + } + + @Test public void testIndirect() { + List lines = ImmutableList.of( + "LDA #$01", + "STA $f0", + "LDA #$cc", + "STA $f1", + "JMP ($00f0) ;dereferences to $cc01"); + + assembler.assembleCode(lines); + assertThat(assembler.hexdump(), equalTo("0600: A9 01 85 F0 A9 CC 85 F1 6C F0 00")); + } + + @Test public void testIndexedIndirect() { + List lines = ImmutableList.of( + "LDX #$01", + "LDA #$05", + "STA $01", + "LDA #$06", + "STA $02", + "LDY #$0a", + "STY $0605", + "LDA ($00,X)"); + + assembler.assembleCode(lines); + assertThat(assembler.hexdump(), + equalTo("0600: A2 01 A9 05 85 01 A9 06 85 02 A0 0A 8C 05 06 A1 \n0610: 00")); + } } diff --git a/app/src/test/java/android/emu6502/LabelsTest.java b/app/src/test/java/android/emu6502/LabelsTest.java new file mode 100644 index 0000000..652c3cd --- /dev/null +++ b/app/src/test/java/android/emu6502/LabelsTest.java @@ -0,0 +1,28 @@ +package android.emu6502; + +import android.emu6502.instructions.Symbols; + +import org.junit.Before; +import org.junit.Test; + +import java.util.Collections; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; + +public class LabelsTest { + + private Labels labels; + private Assembler assembler; + + @Before public void setUp() { + Symbols symbols = new Symbols(); + assembler = new Assembler(new Memory(new Display()), symbols); + labels = new Labels(assembler, symbols); + } + + @Test public void testAddLabel() { + labels.indexLines(Collections.singletonList("test:")); + assertThat(labels.get("test"), equalTo(assembler.getDefaultCodePC())); + } +}