diff --git a/skoolkit/disassembler.py b/skoolkit/disassembler.py index 37ed88fa..90473cb2 100644 --- a/skoolkit/disassembler.py +++ b/skoolkit/disassembler.py @@ -113,16 +113,11 @@ class Disassembler: DEFM statement * `defw_size` - default maximum number of words in a DEFW statement + * `imaker` - callable that returns an instruction object * `opcodes` - comma-separated list of additional opcode sequences to disassemble * `wrap` - if `True`, disassemble an instruction that wraps around the 64K boundary - - The `opcodes` list may contain any of the following hexadecimal opcode - sequences: - - * ``ED70`` - IN F,(C) - * ``ED71`` - OUT (C),0 """ # Component API def __init__(self, snapshot, config): @@ -130,6 +125,7 @@ def __init__(self, snapshot, config): self.defb_size = config.defb_size self.defm_size = config.defm_size self.defw_size = config.defw_size + self.imaker = config.imaker self.wrap = config.wrap self.op_formatter = get_component('OperandFormatter', config) self.defb = 'DEFB ' @@ -163,7 +159,7 @@ def disassemble(self, start, end, base): instructions with two numeric operands (e.g. 'LD (IX+d),n'), the indicator may consist of two letters, one for each operand (e.g. 'dh'). - :return: A list of tuples of the form ``(address, operation, bytes)``. + :return: A list of instruction objects created by *imaker*. """ instructions = [] address = start @@ -174,16 +170,16 @@ def disassemble(self, start, end, base): else: operation, length = decoder(template, address, base) if address + length <= 65536: - instructions.append((address, operation, self.snapshot[address:address + length])) + instructions.append(self.imaker(address, operation, self.snapshot[address:address + length])) elif self.wrap: - instructions.append((address, operation, self.snapshot[address:65536] + self.snapshot[:(address + length) & 65535])) + instructions.append(self.imaker(address, operation, self.snapshot[address:65536] + self.snapshot[:(address + length) & 65535])) else: instructions.append(self._defb_line(address, self.snapshot[address:65536])) address += length return instructions def _defb_line(self, address, data, sublengths=((0, DEFAULT_BASE),), defm=False): - return (address, self.defb_dir(data, sublengths, defm), data) + return self.imaker(address, self.defb_dir(data, sublengths, defm), data) def _defb_lines(self, start, end, sublengths, defm=False): if defm: @@ -210,7 +206,7 @@ def defb_range(self, start, end, sublengths): :param start: The start address. :param end: The end address. :param sublengths: Sequence of sublength identifiers. - :return: A list of tuples of the form ``(address, operation, bytes)``. + :return: A list of instruction objects created by *imaker*. """ return self._defb_lines(start, end, sublengths) @@ -221,7 +217,7 @@ def defm_range(self, start, end, sublengths): :param start: The start address. :param end: The end address. :param sublengths: Sequence of sublength identifiers. - :return: A list of tuples of the form ``(address, operation, bytes)``. + :return: A list of instruction objects created by *imaker*. """ return self._defb_lines(start, end, sublengths, True) @@ -239,7 +235,7 @@ def _defw_lines(self, start, end, sublengths): items.append(self.op_formatter.format_word(data[j] + 256 * data[j + 1], base)) i += length if items: - instructions.insert(0, (start, self.defw + ','.join(items), data)) + instructions.insert(0, self.imaker(start, self.defw + ','.join(items), data)) return instructions # Component API @@ -249,7 +245,7 @@ def defw_range(self, start, end, sublengths): :param start: The start address. :param end: The end address. :param sublengths: Sequence of sublength identifiers. - :return: A list of tuples of the form ``(address, operation, bytes)``. + :return: A list of instruction objects created by *imaker*. """ if sublengths[0][0]: return self._defw_lines(start, end, sublengths) @@ -270,7 +266,7 @@ def defs_range(self, start, end, sublengths): :param start: The start address. :param end: The end address. :param sublengths: Sequence of sublength identifiers. - :return: A list of tuples of the form ``(address, operation, bytes)``. + :return: A list of instruction objects created by *imaker*. """ data = self.snapshot[start:end] values = set(data) @@ -284,7 +280,7 @@ def defs_range(self, start, end, sublengths): elif value: items.append(self.op_formatter.format_byte(value, DEFAULT_BASE)) defs_dir = self.defs + ','.join(items) - return [(start, defs_dir, data)] + return [self.imaker(start, defs_dir, data)] def get_message(self, data): items = [] diff --git a/skoolkit/snaskool.py b/skoolkit/snaskool.py index 9b339052..59093f84 100644 --- a/skoolkit/snaskool.py +++ b/skoolkit/snaskool.py @@ -33,7 +33,7 @@ AD_LABEL_PREFIX = AD_LABEL + '=' AD_REFS_PREFIX = AD_REFS + '=' -DisassemblerConfig = namedtuple('DisassemblerConfig', 'asm_hex asm_lower defb_size defm_size defw_size opcodes wrap') +DisassemblerConfig = namedtuple('DisassemblerConfig', 'asm_hex asm_lower defb_size defm_size defw_size imaker opcodes wrap') # Component API def calculate_references(entries, operations): @@ -130,6 +130,7 @@ def __init__(self, snapshot, ctl_parser, config=None, asm_hex=False, asm_lower=F self.config.get('DefbSize', 8), self.config.get('DefmSize', 65), self.config.get('DefwSize', 1), + Instruction, self.config.get('Opcodes', ''), self.config.get('Wrap', 0) ) @@ -198,7 +199,7 @@ def _create_entries(self): instructions += self.disassembler.defb_range(address, end, sublengths) address += length else: - instructions = [(sub_block.start, '', ())] + instructions = [Instruction(sub_block.start, '', ())] self._add_instructions(sub_block, instructions) sub_blocks = [] @@ -225,11 +226,9 @@ def remove_entry(self, address): if address in self.entry_map: del self.entry_map[address] - def _add_instructions(self, sub_block, specs): - sub_block.instructions = [] - for spec in specs: - instruction = Instruction(*spec) - sub_block.instructions.append(instruction) + def _add_instructions(self, sub_block, instructions): + sub_block.instructions = instructions + for instruction in instructions: self.instructions[instruction.address] = instruction instruction.asm_directives = sub_block.asm_directives.get(instruction.address, ()) for asm_dir in instruction.asm_directives: diff --git a/sphinx/source/components.rst b/sphinx/source/components.rst index 7093119d..64f5de06 100644 --- a/sphinx/source/components.rst +++ b/sphinx/source/components.rst @@ -111,13 +111,16 @@ API methods, in common with skoolkit.disassembler.Disassembler: :members: disassemble, defb_range, defm_range, defs_range, defw_range :noindex: -The 3-element tuples returned by these methods should have the form -``(address, operation, bytes)``, where: +The *imaker* callable used by these methods must accept three positional +arguments: -* ``address`` is the address of the instruction -* ``operation`` is the operation (e.g. 'XOR A', 'DEFB 1') -* ``bytes`` is a sequence of byte values for the instruction (e.g. ``(62, 0)`` - for 'LD A,0') +* ``address`` - the address of the instruction +* ``operation`` - the operation (e.g. 'XOR A', 'DEFB 1') +* ``data`` - a sequence of byte values for the instruction (e.g. (62, 0) for + 'LD A,0') + +The *opcodes* list is defined by the ``Opcodes`` configuration parameter of +:ref:`sna2skool.py `. The *sublengths* argument of the :meth:`defb_range`, :meth:`defm_range`, :meth:`defs_range` and :meth:`defw_range` methods is a sequence of 2-element @@ -136,6 +139,11 @@ If the first element of *sublengths* has a ``size`` value of 0, then the method should produce a list of statements with default sizes (as determined by `defb_size`, `defm_size` and `defw_size`), using the specified base. +.. versionchanged:: 9.3 + Added the *imaker* and *opcodes* attributes to the disassembler + configuration object. API methods must now return objects created by + *imaker*. + .. versionchanged:: 8.5 Added the ability to disassemble an instruction that wraps around the 64K boundary, along with the *wrap* attribute on the disassembler configuration diff --git a/tests/test_disassembler.py b/tests/test_disassembler.py index 15d82e95..fe063947 100644 --- a/tests/test_disassembler.py +++ b/tests/test_disassembler.py @@ -5,7 +5,7 @@ from skoolkit import components from skoolkit.disassembler import Disassembler -Config = namedtuple('Config', 'asm_hex asm_lower defb_size defm_size defw_size opcodes wrap') +Config = namedtuple('Config', 'asm_hex asm_lower defb_size defm_size defw_size imaker opcodes wrap') ASM = { '000000': ('NOP', 'NOP', 'NOP'), @@ -1860,8 +1860,10 @@ ) class DisassemblerTest(SkoolKitTestCase): - def _get_disassembler(self, snapshot=(), asm_hex=False, asm_lower=False, defw_size=1, opcodes='', wrap=False): - config = Config(asm_hex, asm_lower, 8, 66, defw_size, opcodes, wrap) + def _get_disassembler(self, snapshot=(), asm_hex=False, asm_lower=False, defw_size=1, imaker=None, opcodes='', wrap=False): + if imaker is None: + imaker = lambda addr, op, data: (addr, op, data) + config = Config(asm_hex, asm_lower, 8, 66, defw_size, imaker, opcodes, wrap) return Disassembler(snapshot, config) def _get_snapshot(self, start, data): @@ -2060,6 +2062,14 @@ def test_defw_range_with_custom_defw_size_at_64k_boundary(self): instructions = disassembler.defw_range(65529, 65536, ((0, 'n'),)) self.assertEqual(exp_instructions, instructions) + def test_imaker(self): + snapshot = [175] # 00000 XOR A + imaker = lambda addr, op, data: op + disassembler = self._get_disassembler(snapshot, imaker=imaker) + instructions = disassembler.disassemble(0, 1, 'n') + self.assertEqual(len(instructions), 1) + self.assertEqual(instructions[0], 'XOR A') + def test_lower_case_conversion(self): snapshot = [ 0, # 00000 NOP diff --git a/tests/test_snaskool.py b/tests/test_snaskool.py index 360b0c5c..45fd6d5b 100644 --- a/tests/test_snaskool.py +++ b/tests/test_snaskool.py @@ -4826,10 +4826,11 @@ def test_colon_directive_with_non_instruction_comments(self): def test_custom_disassembler(self): custom_disassembler = """ from skoolkit.disassembler import Disassembler + from skoolkit.snaskool import Instruction class CustomDisassembler(Disassembler): def disassemble(self, start, end, base): - return [(start, 'Hi', ())] + return [Instruction(start, 'Hi', ())] """ self.write_component_config('Disassembler', '*.CustomDisassembler', custom_disassembler) snapshot = [0]