From 90d189151b082fbffa9b1fd01d8595309343d7d3 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sun, 5 Apr 2009 12:04:36 -0700 Subject: [PATCH] Reorganized utilities into separate modules. --- src/py65/assembler.py | 2 +- src/py65/monitor.py | 4 +- src/py65/tests/test_assembler.py | 2 +- src/py65/tests/test_disassembler.py | 2 +- src/py65/tests/utils/__init__.py | 1 + .../test_addressing.py} | 41 ++--- src/py65/tests/utils/test_console.py | 12 ++ src/py65/tests/utils/test_conversions.py | 19 ++ src/py65/util.py | 165 ------------------ src/py65/utils/__init__.py | 1 + src/py65/utils/addressing.py | 78 +++++++++ src/py65/utils/console.py | 32 ++++ src/py65/utils/conversions.py | 53 ++++++ 13 files changed, 218 insertions(+), 194 deletions(-) create mode 100644 src/py65/tests/utils/__init__.py rename src/py65/tests/{test_util.py => utils/test_addressing.py} (78%) create mode 100644 src/py65/tests/utils/test_console.py create mode 100644 src/py65/tests/utils/test_conversions.py delete mode 100644 src/py65/util.py create mode 100644 src/py65/utils/__init__.py create mode 100644 src/py65/utils/addressing.py create mode 100644 src/py65/utils/console.py create mode 100644 src/py65/utils/conversions.py diff --git a/src/py65/assembler.py b/src/py65/assembler.py index 052f962..983309b 100644 --- a/src/py65/assembler.py +++ b/src/py65/assembler.py @@ -1,6 +1,6 @@ import re from py65.disassembler import Disassembler -from py65.util import AddressParser +from py65.utils.addressing import AddressParser class Assembler: Statement = re.compile(r'^([A-z]{3}\s+' diff --git a/src/py65/monitor.py b/src/py65/monitor.py index cccf692..2f74069 100644 --- a/src/py65/monitor.py +++ b/src/py65/monitor.py @@ -9,7 +9,9 @@ import sys from py65.mpu6502 import MPU from py65.disassembler import Disassembler from py65.assembler import Assembler -from py65.util import itoa, AddressParser, getch +from py65.utils.addressing import AddressParser +from py65.utils.console import getch +from py65.utils.conversions import itoa from py65.memory import ObservableMemory class Monitor(cmd.Cmd): diff --git a/src/py65/tests/test_assembler.py b/src/py65/tests/test_assembler.py index e48e6b2..3d7524f 100644 --- a/src/py65/tests/test_assembler.py +++ b/src/py65/tests/test_assembler.py @@ -1,7 +1,7 @@ import unittest import sys from py65.assembler import Assembler -from py65.util import AddressParser +from py65.utils.addressing import AddressParser class AssemblerTests(unittest.TestCase): def test_assembles_00(self): diff --git a/src/py65/tests/test_disassembler.py b/src/py65/tests/test_disassembler.py index c3cde00..93dd09b 100644 --- a/src/py65/tests/test_disassembler.py +++ b/src/py65/tests/test_disassembler.py @@ -2,7 +2,7 @@ import unittest import sys from py65.mpu6502 import MPU from py65.disassembler import Disassembler -from py65.util import AddressParser +from py65.utils.addressing import AddressParser class DisassemblerTests(unittest.TestCase): def test_disassembles_00(self): diff --git a/src/py65/tests/utils/__init__.py b/src/py65/tests/utils/__init__.py new file mode 100644 index 0000000..a003759 --- /dev/null +++ b/src/py65/tests/utils/__init__.py @@ -0,0 +1 @@ +# this is a package diff --git a/src/py65/tests/test_util.py b/src/py65/tests/utils/test_addressing.py similarity index 78% rename from src/py65/tests/test_util.py rename to src/py65/tests/utils/test_addressing.py index 881c582..bc1d624 100644 --- a/src/py65/tests/test_util.py +++ b/src/py65/tests/utils/test_addressing.py @@ -1,44 +1,34 @@ 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)) +from py65.utils.addressing import AddressParser class AddressParserTests(unittest.TestCase): def test_number_hex_literal(self): - parser = util.AddressParser() + parser = AddressParser() self.assertEqual(49152, parser.number('$c000')) def test_number_dec_literal(self): - parser = util.AddressParser() + parser = AddressParser() self.assertEqual(49152, parser.number('+49152')) def test_number_bin_literal(self): - parser = util.AddressParser() + parser = AddressParser() self.assertEqual(129, parser.number('%10000001')) def test_number_default_radix(self): - parser = util.AddressParser() + parser = 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 = AddressParser() parser.labels = {'foo': 0xC000} self.assertEquals(0xC000, parser.number('foo')) def test_number_bad_label(self): - parser = util.AddressParser() + parser = AddressParser() try: parser.number('bad_label') self.fail() @@ -46,7 +36,7 @@ class AddressParserTests(unittest.TestCase): self.assertEqual('Label not found: bad_label', why[0]) def test_number_label_hex_offset(self): - parser = util.AddressParser() + parser = AddressParser() parser.labels = {'foo': 0xC000} self.assertEquals(0xC003, parser.number('foo+$3')) self.assertEquals(0xBFFD, parser.number('foo-$3')) @@ -54,7 +44,7 @@ class AddressParserTests(unittest.TestCase): self.assertEquals(0xBFFD, parser.number('foo - $3')) def test_number_label_dec_offset(self): - parser = util.AddressParser() + parser = AddressParser() parser.labels = {'foo': 0xC000} self.assertEquals(0xC003, parser.number('foo++3')) self.assertEquals(0xBFFD, parser.number('foo-+3')) @@ -62,7 +52,7 @@ class AddressParserTests(unittest.TestCase): self.assertEquals(0xBFFD, parser.number('foo - +3')) def test_number_label_bin_offset(self): - parser = util.AddressParser() + parser = AddressParser() parser.labels = {'foo': 0xC000} self.assertEquals(0xC003, parser.number('foo+%00000011')) self.assertEquals(0xBFFD, parser.number('foo-%00000011')) @@ -70,7 +60,7 @@ class AddressParserTests(unittest.TestCase): self.assertEquals(0xBFFD, parser.number('foo - %00000011')) def test_number_label_offset_default_radix(self): - parser = util.AddressParser() + parser = AddressParser() parser.labels = {'foo': 0xC000} parser.radix = 16 self.assertEquals(0xC010, parser.number('foo+10')) @@ -84,7 +74,7 @@ class AddressParserTests(unittest.TestCase): self.assertEquals(0xBFF6, parser.number('foo - 10')) def test_number_bad_label_with_offset(self): - parser = util.AddressParser() + parser = AddressParser() try: parser.number('bad_label+3') self.fail() @@ -92,17 +82,18 @@ class AddressParserTests(unittest.TestCase): self.assertEqual('Label not found: bad_label', why[0]) def test_label_for_returns_label(self): - parser = util.AddressParser(labels={'chrout':0xFFD2}) + parser = AddressParser(labels={'chrout':0xFFD2}) self.assertEqual('chrout', parser.label_for(0xFFD2)) def test_label_for_returns_none_by_default(self): - parser = util.AddressParser(labels={}) + parser = AddressParser(labels={}) self.assertEqual(None, parser.label_for(0xFFD2)) def test_label_for_returns_alternate_default(self): - parser = util.AddressParser(labels={}) + parser = AddressParser(labels={}) self.assertEqual('foo', parser.label_for(0xFFD2, 'foo')) + def test_suite(): return unittest.findTestCases(sys.modules[__name__]) diff --git a/src/py65/tests/utils/test_console.py b/src/py65/tests/utils/test_console.py new file mode 100644 index 0000000..ac863af --- /dev/null +++ b/src/py65/tests/utils/test_console.py @@ -0,0 +1,12 @@ +import unittest +from py65.utils.console import getch + +class ConsoleTopLevelTests(unittest.TestCase): + pass + + +def test_suite(): + return unittest.findTestCases(sys.modules[__name__]) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/src/py65/tests/utils/test_conversions.py b/src/py65/tests/utils/test_conversions.py new file mode 100644 index 0000000..4bc7deb --- /dev/null +++ b/src/py65/tests/utils/test_conversions.py @@ -0,0 +1,19 @@ +import unittest +from py65.utils.conversions import itoa + +class ConversionsTopLevelTests(unittest.TestCase): + def test_itoa_decimal_output(self): + self.assertEqual('10', itoa(10, base=10)) + + def test_itoa_hex_output(self): + self.assertEqual('a', itoa(10, base=16)) + + def test_itoa_bin_output(self): + self.assertEqual('1010', itoa(10, base=2)) + + +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 deleted file mode 100644 index cde80c5..0000000 --- a/src/py65/util.py +++ /dev/null @@ -1,165 +0,0 @@ -import re -import select -import os - -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 label_for(self, address, default=None): - """Given an address, return the corresponding label or a default. - """ - for label, label_address in self.labels.iteritems(): - if label_address == address: - return label - return default - - 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): - """ 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: - num, last_digit = divmod(num, base) - digits.append('0123456789abcdefghijklmnopqrstuvwxyz'[last_digit]) - if negative: - digits.append('-') - digits.reverse() - return ''.join(digits) - -def convert_to_bin(bcd): - return bcd2bin[bcd] - -def convert_to_bcd(bin): - return bin2bcd[bin] - -bcd2bin = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, # 0x00 - 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, # 0x10 - 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, # 0x20 - 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, # 0x30 - 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, # 0x40 - 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, # 0x50 - 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, # 0x60 - 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, # 0x70 - 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, # 0x80 - 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,100,101,102,103,104,105, # 0x90 - 100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115, # 0xA0 - 110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125, # 0xB0 - 120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135, # 0xC0 - 130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145, # 0xD0 - 140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155, # 0xE0 - 150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165 # 0xF0 -] - -bin2bcd = [ - 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09, - 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19, - 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29, - 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39, - 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49, - 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59, - 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69, - 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79, - 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89, - 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99 -] - -def getch(stdin): - """ Performs a nonblocking read of one byte from stdin and returns - its ordinal value. If no byte is available, 0 is returned. - """ - import termios - import fcntl - - fd = stdin.fileno() - - oldterm = termios.tcgetattr(fd) - newattr = oldterm[:] - newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO - termios.tcsetattr(fd, termios.TCSANOW, newattr) - - oldflags = fcntl.fcntl(fd, fcntl.F_GETFL) - fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK) - - try: - byte = 0 - r, w, e = select.select([fd], [], [], 0.1) - if r: - c = stdin.read(1) - byte = ord(c) - if byte == 0x0a: - byte = 0x0d - finally: - termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm) - fcntl.fcntl(fd, fcntl.F_SETFL, oldflags) - return byte diff --git a/src/py65/utils/__init__.py b/src/py65/utils/__init__.py new file mode 100644 index 0000000..a003759 --- /dev/null +++ b/src/py65/utils/__init__.py @@ -0,0 +1 @@ +# this is a package diff --git a/src/py65/utils/addressing.py b/src/py65/utils/addressing.py new file mode 100644 index 0000000..d331797 --- /dev/null +++ b/src/py65/utils/addressing.py @@ -0,0 +1,78 @@ +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 label_for(self, address, default=None): + """Given an address, return the corresponding label or a default. + """ + for label, label_address in self.labels.iteritems(): + if label_address == address: + return label + return default + + 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) diff --git a/src/py65/utils/console.py b/src/py65/utils/console.py new file mode 100644 index 0000000..69ca3ab --- /dev/null +++ b/src/py65/utils/console.py @@ -0,0 +1,32 @@ +import select +import os + +def getch(stdin): + """ Performs a nonblocking read of one byte from stdin and returns + its ordinal value. If no byte is available, 0 is returned. + """ + import termios + import fcntl + + fd = stdin.fileno() + + oldterm = termios.tcgetattr(fd) + newattr = oldterm[:] + newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO + termios.tcsetattr(fd, termios.TCSANOW, newattr) + + oldflags = fcntl.fcntl(fd, fcntl.F_GETFL) + fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK) + + try: + byte = 0 + r, w, e = select.select([fd], [], [], 0.1) + if r: + c = stdin.read(1) + byte = ord(c) + if byte == 0x0a: + byte = 0x0d + finally: + termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm) + fcntl.fcntl(fd, fcntl.F_SETFL, oldflags) + return byte diff --git a/src/py65/utils/conversions.py b/src/py65/utils/conversions.py new file mode 100644 index 0000000..47bccbe --- /dev/null +++ b/src/py65/utils/conversions.py @@ -0,0 +1,53 @@ +def itoa(num, base=10): + """ 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: + num, last_digit = divmod(num, base) + digits.append('0123456789abcdefghijklmnopqrstuvwxyz'[last_digit]) + if negative: + digits.append('-') + digits.reverse() + return ''.join(digits) + +def convert_to_bin(bcd): + return bcd2bin[bcd] + +def convert_to_bcd(bin): + return bin2bcd[bin] + +bcd2bin = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, # 0x00 + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, # 0x10 + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, # 0x20 + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, # 0x30 + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, # 0x40 + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, # 0x50 + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, # 0x60 + 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, # 0x70 + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, # 0x80 + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,100,101,102,103,104,105, # 0x90 + 100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115, # 0xA0 + 110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125, # 0xB0 + 120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135, # 0xC0 + 130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145, # 0xD0 + 140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155, # 0xE0 + 150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165 # 0xF0 +] + +bin2bcd = [ + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09, + 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19, + 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29, + 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39, + 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49, + 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69, + 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79, + 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99 +]