From 94cb64320a07001b04d22efa7c1db0f26a8782fd Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Wed, 10 Sep 2008 03:54:36 +0000 Subject: [PATCH] Extracted address parsing from Monitor into util.AddressParser. --- src/py65/monitor.py | 85 +++++++---------------------- src/py65/tests/test_monitor.py | 78 +-------------------------- src/py65/tests/test_util.py | 99 ++++++++++++++++++++++++++++++++++ src/py65/util.py | 94 ++++++++++++++++++++++++++++---- 4 files changed, 202 insertions(+), 154 deletions(-) create mode 100644 src/py65/tests/test_util.py diff --git a/src/py65/monitor.py b/src/py65/monitor.py index 865d433..51985b0 100644 --- a/src/py65/monitor.py +++ b/src/py65/monitor.py @@ -6,7 +6,7 @@ import re import shlex import asyncore from py65.mpu6502 import MPU -from py65.util import itoa +from py65.util import itoa, AddressParser from py65.memory import ObservableMemory class Monitor(cmd.Cmd): @@ -16,8 +16,7 @@ class Monitor(cmd.Cmd): self._mpu = MPU() self._install_mpu_observers() self._update_prompt() - self._radix = 16 - self._labels = {} + self._address_parser = AddressParser() cmd.Cmd.__init__(self, completekey, stdin, stdout) def onecmd(self, line): @@ -115,7 +114,7 @@ class Monitor(cmd.Cmd): self._run(stopcodes=returns) def do_goto(self, args): - self._mpu.pc = self._parsenum(args) + self._mpu.pc = self._address_parser.number(args) brks = [0x00] # BRK self._run(stopcodes=brks) @@ -144,13 +143,13 @@ class Monitor(cmd.Cmd): changed = False for name, radix in radixes.iteritems(): if name[0].lower() == new: - self._radix = radix + self._address_parser.radix = radix changed = True if not changed: self._output("Illegal radix: %s" % args) for name, radix in radixes.iteritems(): - if self._radix == radix: + if self._address_parser.radix == radix: self._output("Default radix is %s" % name) def help_tilde(self): @@ -159,7 +158,7 @@ class Monitor(cmd.Cmd): def do_tilde(self, args): try: - num = self._parsenum(args) + num = self._address_parser.number(args) except ValueError: self._output("Syntax error: %s" % args) return @@ -187,7 +186,7 @@ class Monitor(cmd.Cmd): self._output("Invalid register: %s" % register) else: try: - intval = self._parsenum(value) & 0xFFFF + intval = self._address_parser.number(value) & 0xFFFF if len(register) == 1: intval &= 0xFF setattr(self._mpu, register, intval) @@ -226,7 +225,7 @@ class Monitor(cmd.Cmd): filename = split[0] if len(split) == 2: - start = self._parsenum(split[1]) + start = self._address_parser.number(split[1]) else: start = self._mpu.pc @@ -253,8 +252,8 @@ class Monitor(cmd.Cmd): self._output("Syntax error: %s" % args) return - start, end = self._parserange(split[0]) - filler = map(self._parsenum, split[1:]) + start, end = self._address_parser.range(split[0]) + filler = map(self._address_parser.number, split[1:]) self._fill(start, end, filler) @@ -283,7 +282,7 @@ class Monitor(cmd.Cmd): self._output("Display the contents of memory.") def do_mem(self, args): - start, end = self._parserange(args) + start, end = self._address_parser.range(args) out = itoa(start, 16).zfill(4) + ": " for byte in self._mpu.memory[start:end+1]: @@ -296,74 +295,26 @@ class Monitor(cmd.Cmd): self._output("Syntax error: %s" % args) return - address = self._parsenum(split[0]) + address = self._address_parser.number(split[0]) label = split[1] - self._labels[label] = address + self._address_parser.labels[label] = address def do_show_labels(self, args): - byaddress = zip(self._labels.values(), self._labels.keys()) + values = self._address_parser.labels.values() + keys = self._address_parser.labels.keys() + + byaddress = zip(values, keys) byaddress.sort() for address, label in byaddress: self._output("%04x: %s" % (address, label)) def do_delete_label(self, args): try: - del self._labels[args] + del self._address_parser.labels[args] except KeyError: pass - def _parsenum(self, num): - if num.startswith('$'): - return int(num[1:], 16) - - elif num.startswith('+'): - return int(num[1:], 10) - - elif num.startswith('%'): - return int(num[1:], 2) - - elif num in self._labels: - return self._labels[num] - - else: - matches = re.match('^([^\s+-]+)\s*([+\-])\s*([$+%]?\d+)$', num) - if matches: - label, sign, offset = matches.groups() - - if label not in self._labels: - raise KeyError("Label not found: %s" % label) - - base = self._labels[label] - offset = self._parsenum(offset) - - if sign == '+': - address = base + offset - else: - address = base - offset - - if address < 0: - address = 0 - if address > 0xFFFF: - address = 0xFFFF - return address - - else: - try: - return int(num, self._radix) - except ValueError: - raise KeyError("Label not found: %s" % num) - - def _parserange(self, addresses): - matches = re.match('^([^:,]+)\s*[:,]+\s*([^:,]+)$', addresses) - if matches: - start, end = map(self._parsenum, matches.groups(0)) - else: - start = end = self._parsenum(addresses) - - if start > end: - start, end = end, start - return (start, end) def main(args=None, options=None): diff --git a/src/py65/tests/test_monitor.py b/src/py65/tests/test_monitor.py index 5009fe9..d4bf24f 100644 --- a/src/py65/tests/test_monitor.py +++ b/src/py65/tests/test_monitor.py @@ -4,83 +4,7 @@ import re from py65.monitor import Monitor class MonitorTests(unittest.TestCase): - def test__parsenum_hex_literal(self): - mon = Monitor() - self.assertEqual(49152, mon._parsenum('$c000')) - - def test__parsenum_dec_literal(self): - mon = Monitor() - self.assertEqual(49152, mon._parsenum('+49152')) - - def test__parsenum_bin_literal(self): - mon = Monitor() - self.assertEqual(129, mon._parsenum('%10000001')) - - def test__parsenum_default_radix(self): - mon = Monitor() - mon._radix = 10 - self.assertEqual(10, mon._parsenum('10')) - mon._radix = 16 - self.assertEqual(16, mon._parsenum('10')) - - def test__parsenum_label(self): - mon = Monitor() - mon._labels = {'foo': 0xC000} - self.assertEquals(0xC000, mon._parsenum('foo')) - - def test__parsenum_bad_label(self): - mon = Monitor() - try: - mon._parsenum('bad_label') - self.fail() - except KeyError, why: - self.assertEqual('Label not found: bad_label', why[0]) - - def test__parsenum_label_hex_offset(self): - mon = Monitor() - mon._labels = {'foo': 0xC000} - self.assertEquals(0xC003, mon._parsenum('foo+$3')) - self.assertEquals(0xBFFD, mon._parsenum('foo-$3')) - self.assertEquals(0xC003, mon._parsenum('foo + $3')) - self.assertEquals(0xBFFD, mon._parsenum('foo - $3')) - - def test__parsenum_label_dec_offset(self): - mon = Monitor() - mon._labels = {'foo': 0xC000} - self.assertEquals(0xC003, mon._parsenum('foo++3')) - self.assertEquals(0xBFFD, mon._parsenum('foo-+3')) - self.assertEquals(0xC003, mon._parsenum('foo + +3')) - self.assertEquals(0xBFFD, mon._parsenum('foo - +3')) - - def test__parsenum_label_bin_offset(self): - mon = Monitor() - mon._labels = {'foo': 0xC000} - self.assertEquals(0xC003, mon._parsenum('foo+%00000011')) - self.assertEquals(0xBFFD, mon._parsenum('foo-%00000011')) - self.assertEquals(0xC003, mon._parsenum('foo + %00000011')) - self.assertEquals(0xBFFD, mon._parsenum('foo - %00000011')) - - def test__parsenum_label_offset_default_radix(self): - mon = Monitor() - mon._labels = {'foo': 0xC000} - mon._radix = 16 - self.assertEquals(0xC010, mon._parsenum('foo+10')) - self.assertEquals(0xBFF0, mon._parsenum('foo-10')) - self.assertEquals(0xC010, mon._parsenum('foo + 10')) - self.assertEquals(0xBFF0, mon._parsenum('foo - 10')) - mon._radix = 10 - self.assertEquals(0xC00A, mon._parsenum('foo+10')) - self.assertEquals(0xBFF6, mon._parsenum('foo-10')) - self.assertEquals(0xC00A, mon._parsenum('foo + 10')) - self.assertEquals(0xBFF6, mon._parsenum('foo - 10')) - - def test__parsenum_bad_label_with_offset(self): - mon = Monitor() - try: - mon._parsenum('bad_label+3') - self.fail() - except KeyError, why: - self.assertEqual('Label not found: bad_label', why[0]) + pass def test_suite(): return unittest.findTestCases(sys.modules[__name__]) diff --git a/src/py65/tests/test_util.py b/src/py65/tests/test_util.py new file mode 100644 index 0000000..1a39b18 --- /dev/null +++ b/src/py65/tests/test_util.py @@ -0,0 +1,99 @@ +import unittest +import sys +from py65 import util + +class UtilTopLevelTests(unittest.TestCase): + def test_itoa_decimal_output(self): + self.assertEqual('10', util.itoa(10, base=10)) + + def test_itoa_hex_output(self): + self.assertEqual('a', util.itoa(10, base=16)) + + def test_itoa_bin_output(self): + self.assertEqual('1010', util.itoa(10, base=2)) + +class AddressParserTests(unittest.TestCase): + def test_number_hex_literal(self): + parser = util.AddressParser() + self.assertEqual(49152, parser.number('$c000')) + + def test_number_dec_literal(self): + parser = util.AddressParser() + self.assertEqual(49152, parser.number('+49152')) + + def test_number_bin_literal(self): + parser = util.AddressParser() + self.assertEqual(129, parser.number('%10000001')) + + def test_number_default_radix(self): + parser = util.AddressParser() + parser.radix = 10 + self.assertEqual(10, parser.number('10')) + parser.radix = 16 + self.assertEqual(16, parser.number('10')) + + def test_number_label(self): + parser = util.AddressParser() + parser.labels = {'foo': 0xC000} + self.assertEquals(0xC000, parser.number('foo')) + + def test_number_bad_label(self): + parser = util.AddressParser() + try: + parser.number('bad_label') + self.fail() + except KeyError, why: + self.assertEqual('Label not found: bad_label', why[0]) + + def test_number_label_hex_offset(self): + parser = util.AddressParser() + parser.labels = {'foo': 0xC000} + self.assertEquals(0xC003, parser.number('foo+$3')) + self.assertEquals(0xBFFD, parser.number('foo-$3')) + self.assertEquals(0xC003, parser.number('foo + $3')) + self.assertEquals(0xBFFD, parser.number('foo - $3')) + + def test_number_label_dec_offset(self): + parser = util.AddressParser() + parser.labels = {'foo': 0xC000} + self.assertEquals(0xC003, parser.number('foo++3')) + self.assertEquals(0xBFFD, parser.number('foo-+3')) + self.assertEquals(0xC003, parser.number('foo + +3')) + self.assertEquals(0xBFFD, parser.number('foo - +3')) + + def test_number_label_bin_offset(self): + parser = util.AddressParser() + parser.labels = {'foo': 0xC000} + self.assertEquals(0xC003, parser.number('foo+%00000011')) + self.assertEquals(0xBFFD, parser.number('foo-%00000011')) + self.assertEquals(0xC003, parser.number('foo + %00000011')) + self.assertEquals(0xBFFD, parser.number('foo - %00000011')) + + def test_number_label_offset_default_radix(self): + parser = util.AddressParser() + parser.labels = {'foo': 0xC000} + parser.radix = 16 + self.assertEquals(0xC010, parser.number('foo+10')) + self.assertEquals(0xBFF0, parser.number('foo-10')) + self.assertEquals(0xC010, parser.number('foo + 10')) + self.assertEquals(0xBFF0, parser.number('foo - 10')) + parser.radix = 10 + self.assertEquals(0xC00A, parser.number('foo+10')) + self.assertEquals(0xBFF6, parser.number('foo-10')) + self.assertEquals(0xC00A, parser.number('foo + 10')) + self.assertEquals(0xBFF6, parser.number('foo - 10')) + + def test_number_bad_label_with_offset(self): + parser = util.AddressParser() + try: + parser.number('bad_label+3') + self.fail() + except KeyError, why: + self.assertEqual('Label not found: bad_label', why[0]) + + +def test_suite(): + return unittest.findTestCases(sys.modules[__name__]) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/src/py65/util.py b/src/py65/util.py index d4af422..e05d126 100644 --- a/src/py65/util.py +++ b/src/py65/util.py @@ -1,22 +1,96 @@ +import re +class AddressParser: + """Parse user input into addresses or ranges of addresses. + """ + + def __init__(self, radix=16, labels={}): + """Radix is the default radix to use when one is not specified + as a prefix of any input. Labels are a dictionary of label + names that can be substituted for addresses. + """ + self.radix = radix + self.labels = labels + + def number(self, num): + """Parse a string containing a label or number into an address. + """ + if num.startswith('$'): + return int(num[1:], 16) + + elif num.startswith('+'): + return int(num[1:], 10) + + elif num.startswith('%'): + return int(num[1:], 2) + + elif num in self.labels: + return self.labels[num] + + else: + matches = re.match('^([^\s+-]+)\s*([+\-])\s*([$+%]?\d+)$', num) + if matches: + label, sign, offset = matches.groups() + + if label not in self.labels: + raise KeyError("Label not found: %s" % label) + + base = self.labels[label] + offset = self.number(offset) + + if sign == '+': + address = base + offset + else: + address = base - offset + + if address < 0: + address = 0 + if address > 0xFFFF: + address = 0xFFFF + return address + + else: + try: + return int(num, self.radix) + except ValueError: + raise KeyError("Label not found: %s" % num) + + def range(self, addresses): + """Parse a string containing an address or a range of addresses + into a tuple of (start address, end address) + """ + matches = re.match('^([^:,]+)\s*[:,]+\s*([^:,]+)$', addresses) + if matches: + start, end = map(self.number, matches.groups(0)) + else: + start = end = self.number(addresses) + + if start > end: + start, end = end, start + return (start, end) + + def itoa(num, base=10): - negative = num < 0 - if negative: + """ Convert a decimal number to its equivalent in another base. + This is essentially the inverse of int(num, base). + """ + negative = num < 0 + if negative: num = -num - digits = [] - while num > 0: + digits = [] + while num > 0: num, last_digit = divmod(num, base) digits.append('0123456789abcdefghijklmnopqrstuvwxyz'[last_digit]) - if negative: + if negative: digits.append('-') - digits.reverse() - return ''.join(digits) - + digits.reverse() + return ''.join(digits) + def convert_to_bin(bcd): - return bcd2bin[bcd] + return bcd2bin[bcd] def convert_to_bcd(bin): - return bin2bcd[bin] + return bin2bcd[bin] bcd2bin = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, # 0x00