Skip to content

Commit

Permalink
Add the 'imaker' attribute to the Disassembler configuration object
Browse files Browse the repository at this point in the history
  • Loading branch information
skoolkid committed Jun 6, 2024
1 parent 32cdcc0 commit 6ae3247
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 33 deletions.
28 changes: 12 additions & 16 deletions skoolkit/disassembler.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,23 +113,19 @@ 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):
self.snapshot = snapshot
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 '
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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)

Expand All @@ -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)

Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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 = []
Expand Down
13 changes: 6 additions & 7 deletions skoolkit/snaskool.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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)
)
Expand Down Expand Up @@ -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 = []
Expand All @@ -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:
Expand Down
20 changes: 14 additions & 6 deletions sphinx/source/components.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <sna2skool-conf>`.

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
Expand All @@ -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
Expand Down
16 changes: 13 additions & 3 deletions tests/test_disassembler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion tests/test_snaskool.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down

0 comments on commit 6ae3247

Please sign in to comment.