mirror of
https://github.com/felipecsl/6502Android.git
synced 2025-01-06 12:31:49 +00:00
Fixes labels, adds more tests
This commit is contained in:
parent
60c9b254a6
commit
a7769ba7f0
@ -2,18 +2,28 @@ package android.emu6502
|
|||||||
|
|
||||||
import android.emu6502.instructions.Instruction
|
import android.emu6502.instructions.Instruction
|
||||||
import android.emu6502.instructions.Opcodes
|
import android.emu6502.instructions.Opcodes
|
||||||
|
import android.emu6502.instructions.Symbols
|
||||||
|
import java.util.HashMap
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
import kotlin.text.Regex
|
import kotlin.text.Regex
|
||||||
|
|
||||||
class Assembler(private var labels: Labels, private var memory: Memory,
|
class Assembler(private var memory: Memory, private val symbols: Symbols) {
|
||||||
private var symbols: Symbols) {
|
|
||||||
|
|
||||||
var codeLen = 0
|
var codeLen = 0
|
||||||
val BOOTSTRAP_ADDRESS = 0x600
|
val BOOTSTRAP_ADDRESS = 0x600
|
||||||
var defaultCodePC = BOOTSTRAP_ADDRESS
|
var defaultCodePC = BOOTSTRAP_ADDRESS
|
||||||
|
private var labels = Labels(this, symbols)
|
||||||
|
|
||||||
fun assembleCode(lines: List<String>) {
|
fun assembleCode(lines: List<String>) {
|
||||||
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)) {
|
if (!assembleLine(line)) {
|
||||||
throw RuntimeException("**Syntax error line " + (i + 1) + ": " + 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)
|
memory.set(defaultCodePC, 0x00)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun assembleLine(line: String): Boolean {
|
private fun preprocess(lines: List<String>): List<String> {
|
||||||
|
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 input = line
|
||||||
var command: String
|
var command: String
|
||||||
var param: String
|
var param: String
|
||||||
@ -56,7 +83,7 @@ class Assembler(private var labels: Labels, private var memory: Memory,
|
|||||||
} else {
|
} else {
|
||||||
addr = Integer.parseInt(param, 10)
|
addr = Integer.parseInt(param, 10)
|
||||||
}
|
}
|
||||||
if ((addr < 0) || (addr > 0xffff)) {
|
if (addr < 0 || addr > 0xffff) {
|
||||||
throw IllegalStateException("Unable to relocate code outside 64k memory")
|
throw IllegalStateException("Unable to relocate code outside 64k memory")
|
||||||
}
|
}
|
||||||
defaultCodePC = addr
|
defaultCodePC = addr
|
||||||
@ -73,7 +100,7 @@ class Assembler(private var labels: Labels, private var memory: Memory,
|
|||||||
|
|
||||||
param = param.replace("[ ]".toRegex(), "")
|
param = param.replace("[ ]".toRegex(), "")
|
||||||
|
|
||||||
if (command === "DCB") {
|
if (command == "DCB") {
|
||||||
return DCB(param)
|
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.
|
"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 {
|
private fun checkBranch(param: String, opcode: Int): Boolean {
|
||||||
throw UnsupportedOperationException(
|
if (opcode == 0xff) {
|
||||||
"not implemented") //To change body of created functions use File | Settings | File Templates.
|
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 {
|
private fun checkAbsolute(param: String, opcode: Int): Boolean {
|
||||||
|
if (opcode == 0xff) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if (checkWordOperand(param, opcode, "^([\\w\\$]+)$")) {
|
if (checkWordOperand(param, opcode, "^([\\w\\$]+)$")) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -183,8 +233,8 @@ class Assembler(private var labels: Labels, private var memory: Memory,
|
|||||||
if (param.matches(regex)) {
|
if (param.matches(regex)) {
|
||||||
val finalParam = param.replace(",Y", "", true).replace(",X", "", true)
|
val finalParam = param.replace(",Y", "", true).replace(",X", "", true)
|
||||||
pushByte(opcode)
|
pushByte(opcode)
|
||||||
if (labels.find(finalParam)) {
|
val addr = labels.get(finalParam)
|
||||||
val addr = (labels.getPC(finalParam))
|
if (addr != null) {
|
||||||
if (addr < 0 || addr > 0xffff) {
|
if (addr < 0 || addr > 0xffff) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -269,8 +319,8 @@ class Assembler(private var labels: Labels, private var memory: Memory,
|
|||||||
var label = param.replace("^#[<>](\\w+)$".toRegex(), "$1")
|
var label = param.replace("^#[<>](\\w+)$".toRegex(), "$1")
|
||||||
var hilo = param.replace("^#([<>]).*$".toRegex(), "$1")
|
var hilo = param.replace("^#([<>]).*$".toRegex(), "$1")
|
||||||
pushByte(opcode)
|
pushByte(opcode)
|
||||||
if (labels.find(label)) {
|
val addr = labels.get(label)
|
||||||
var addr = labels.getPC(label)
|
if (addr != null) {
|
||||||
when (hilo) {
|
when (hilo) {
|
||||||
">" -> {
|
">" -> {
|
||||||
pushByte(addr.shr(8).and(0xFF))
|
pushByte(addr.shr(8).and(0xFF))
|
||||||
@ -296,7 +346,7 @@ class Assembler(private var labels: Labels, private var memory: Memory,
|
|||||||
var parameter = param
|
var parameter = param
|
||||||
|
|
||||||
if (parameter.matches("^\\w+$".toRegex())) {
|
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) {
|
if (lookupVal != null) {
|
||||||
parameter = lookupVal
|
parameter = lookupVal
|
||||||
}
|
}
|
||||||
@ -328,7 +378,7 @@ class Assembler(private var labels: Labels, private var memory: Memory,
|
|||||||
var parameter = param
|
var parameter = param
|
||||||
|
|
||||||
if (parameter.matches("^\\w+$".toRegex())) {
|
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) {
|
if (lookupVal != null) {
|
||||||
parameter = lookupVal
|
parameter = lookupVal
|
||||||
}
|
}
|
||||||
@ -368,7 +418,7 @@ class Assembler(private var labels: Labels, private var memory: Memory,
|
|||||||
|
|
||||||
private fun checkSingle(param: String, opcode: Int): Boolean {
|
private fun checkSingle(param: String, opcode: Int): Boolean {
|
||||||
// Accumulator instructions are counted as single-byte opcodes
|
// Accumulator instructions are counted as single-byte opcodes
|
||||||
if (!param.equals("") && !param.equals("A")) {
|
if (!param.equals("") && !param.equals("A")) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
pushByte(opcode)
|
pushByte(opcode)
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package android.emu6502
|
package android.emu6502
|
||||||
|
|
||||||
|
import android.emu6502.instructions.Symbols
|
||||||
|
|
||||||
final class Emulator {
|
final class Emulator {
|
||||||
val display = Display()
|
val display = Display()
|
||||||
val memory = Memory(display)
|
val memory = Memory(display)
|
||||||
val cpu = CPU(memory)
|
val cpu = CPU(memory)
|
||||||
val assembler = Assembler(Labels(), memory, Symbols())
|
val assembler = Assembler(memory, Symbols())
|
||||||
}
|
}
|
@ -1,13 +1,39 @@
|
|||||||
package android.emu6502
|
package android.emu6502
|
||||||
|
|
||||||
class Labels {
|
import android.emu6502.instructions.Symbols
|
||||||
fun getPC(label: String): Int {
|
import java.util.*
|
||||||
throw UnsupportedOperationException(
|
|
||||||
"not implemented") //To change body of created functions use File | Settings | File Templates.
|
class Labels(private val assembler: Assembler, private val symbols: Symbols) : HashMap<String, Int>() {
|
||||||
|
private val labelIndex: ArrayList<String> = ArrayList()
|
||||||
|
|
||||||
|
fun indexLines(lines: List<String>) {
|
||||||
|
lines.forEach { line ->
|
||||||
|
indexLine(line)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun find(label: String): Boolean {
|
fun get(label: String): Int {
|
||||||
throw UnsupportedOperationException(
|
return super.get(label) ?: -1
|
||||||
"not implemented") //To change body of created functions use File | Settings | File Templates.
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,13 +0,0 @@
|
|||||||
package android.emu6502
|
|
||||||
|
|
||||||
class Symbols {
|
|
||||||
private val table: Map<String, String> = emptyMap()
|
|
||||||
|
|
||||||
fun lookup(key: String): String? {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun add(key: String, value: String) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,6 @@
|
|||||||
|
package android.emu6502.instructions
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class Symbols : HashMap<String, String>() {
|
||||||
|
}
|
@ -2,6 +2,9 @@ package android.emu6502;
|
|||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
|
import android.emu6502.instructions.Symbols;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -11,7 +14,13 @@ import static org.junit.Assert.assertThat;
|
|||||||
|
|
||||||
public class AssemblerTest {
|
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<String> lines = ImmutableList.of(
|
List<String> lines = ImmutableList.of(
|
||||||
"LDA #$01",
|
"LDA #$01",
|
||||||
"STA $0200",
|
"STA $0200",
|
||||||
@ -19,8 +28,73 @@ public class AssemblerTest {
|
|||||||
"STA $0201",
|
"STA $0201",
|
||||||
"LDA #$08",
|
"LDA #$08",
|
||||||
"STA $0202");
|
"STA $0202");
|
||||||
Assembler assembler = new Assembler(new Labels(), new Memory(new Display()), new Symbols());
|
|
||||||
assembler.assembleCode(lines);
|
assembler.assembleCode(lines);
|
||||||
assertThat(assembler.hexdump(), equalTo("0600: A9 01 8D 00 02 A9 05 8D 01 02 A9 08 8D 02 02"));
|
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<String> 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<String> 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<String> 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<String> 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<String> 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"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
28
app/src/test/java/android/emu6502/LabelsTest.java
Normal file
28
app/src/test/java/android/emu6502/LabelsTest.java
Normal file
@ -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()));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user