Skip to content

Commit

Permalink
Implement the @bytes directive
Browse files Browse the repository at this point in the history
  • Loading branch information
skoolkid committed Jun 14, 2024
1 parent 2616903 commit effedbb
Show file tree
Hide file tree
Showing 14 changed files with 367 additions and 17 deletions.
25 changes: 17 additions & 8 deletions skoolkit/skool2bin.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2015-2021, 2023 Richard Dymond (rjdymond@gmail.com)
# Copyright 2015-2021, 2023, 2024 Richard Dymond (rjdymond@gmail.com)
#
# This file is part of SkoolKit.
#
Expand All @@ -22,7 +22,7 @@
from skoolkit.components import get_assembler, get_instruction_utility
from skoolkit.skoolmacro import MacroParsingError, parse_if
from skoolkit.skoolutils import (DIRECTIVES, Memory, parse_address_range, parse_asm_bank_directive,
parse_asm_data_directive, parse_asm_keep_directive,
parse_asm_bytes_directive, parse_asm_data_directive, parse_asm_keep_directive,
parse_asm_nowarn_directive, parse_asm_sub_fix_directive, read_skool)
from skoolkit.textutils import partition_unquoted

Expand All @@ -31,14 +31,15 @@
Entry = namedtuple('Entry', 'ctl instructions')

class Instruction:
def __init__(self, skool_address, address=None, operation=None, sub=False, keep=None, nowarn=None, data=None, marker=' '):
def __init__(self, skool_address, address=None, operation=None, sub=False, keep=None, nowarn=None, bvalues=None, data=None, marker=' '):
self.address = skool_address # API (InstructionUtility)
self.operation = operation # API (InstructionUtility)
self.sub = sub # API (InstructionUtility)
self.keep = keep # API (InstructionUtility)
self.nowarn = nowarn # API (InstructionUtility)
self.original = operation
self.real_address = address
self.bvalues = bvalues
self.data = data
self.marker = marker

Expand Down Expand Up @@ -93,6 +94,7 @@ def _reset(self, data):
self.subs = defaultdict(list, {(0, 0): ()})
self.keep = None
self.nowarn = None
self.bvalues = None
if data:
self.data = []
else:
Expand Down Expand Up @@ -152,14 +154,16 @@ def _add_instructions(self, address, skool_address, operations, original_op, rem
else:
operation, sub = original_op, False
if operation:
address += self._get_size(operation, address, ' ', overwrite, removed, offset, sub, skool_address)
address += self._get_size(operation, address, ' ', overwrite, removed, offset, sub, skool_address, self.bvalues)
for overwrite, operation, append in after:
if operation:
address += self._get_size(operation, address, '+', overwrite, removed, offset)
return address

def _get_size(self, operation, address, marker, overwrite=False, removed=None, offset=0, sub=True, skool_address=None):
if operation.upper().startswith(('DJNZ ', 'JR ')):
def _get_size(self, operation, address, marker, overwrite=False, removed=None, offset=0, sub=True, skool_address=None, bvalues=None):
if bvalues:
size = len(bvalues)
elif operation.upper().startswith(('DJNZ ', 'JR ')):
size = 2
else:
size = self.assembler.get_size(operation, address)
Expand All @@ -168,7 +172,7 @@ def _get_size(self, operation, address, marker, overwrite=False, removed=None, o
removed.update(range(address + offset, address + offset + size))
marker = '|'
if self.start <= address < self.end:
self.instructions.append(Instruction(skool_address, address, operation, sub, self.keep, self.nowarn, self.data, marker))
self.instructions.append(Instruction(skool_address, address, operation, sub, self.keep, self.nowarn, bvalues, self.data, marker))
return size
raise SkoolParsingError("Failed to assemble:\n {} {}".format(address, operation))

Expand Down Expand Up @@ -199,6 +203,8 @@ def _parse_asm_directive(self, address, directive, removed):
self.keep = parse_asm_keep_directive(directive)
elif directive.startswith('nowarn'):
self.nowarn = parse_asm_nowarn_directive(directive)
elif directive.startswith('bytes='):
self.bvalues = parse_asm_bytes_directive(directive)
elif self.data is not None and directive.startswith(('defb=', 'defs=', 'defw=')):
self.data.append(directive)
elif directive.startswith('remote='):
Expand Down Expand Up @@ -239,7 +245,10 @@ def _relocate(self):
address, data = parse_asm_data_directive(self.snapshot, address, data_dir, False)
self._poke(Instruction(None, address, '@' + data_dir), data)
address += len(data)
self._poke(i, self.assembler.assemble(i.operation, i.real_address))
if i.bvalues:
self._poke(i, i.bvalues)
else:
self._poke(i, self.assembler.assemble(i.operation, i.real_address))

def write(self, binfile):
if len(self.snapshot) == 0x20000:
Expand Down
9 changes: 7 additions & 2 deletions skoolkit/skoolctl.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2010-2023 Richard Dymond (rjdymond@gmail.com)
# Copyright 2010-2024 Richard Dymond (rjdymond@gmail.com)
#
# This file is part of SkoolKit.
#
Expand Down Expand Up @@ -634,7 +634,12 @@ def _parse_skool(self, skoolfile, min_address, max_address):
last_instruction = end_instruction
if last_entry is not None and last_entry.ctl != 'i':
address = last_instruction.address
self.end_address = address + (self.assembler.get_size(last_instruction.operation, address) or 1)
for asm_directive in last_instruction.asm_directives:
if asm_directive.startswith('bytes='):
self.end_address = address + asm_directive.count(',') + 1
break
else:
self.end_address = address + (self.assembler.get_size(last_instruction.operation, address) or 1)

parse_address_comments(address_comments, self.keep_lines)

Expand Down
21 changes: 15 additions & 6 deletions skoolkit/skoolparser.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2008-2023 Richard Dymond (rjdymond@gmail.com)
# Copyright 2008-2024 Richard Dymond (rjdymond@gmail.com)
#
# This file is part of SkoolKit.
#
Expand All @@ -25,9 +25,9 @@
from skoolkit.skoolmacro import INTEGER, MacroParsingError, parse_if
from skoolkit.skoolutils import (DIRECTIVES, Z80_ASSEMBLER, Comment, Memory, get_address, join_comments,
parse_address_comments, parse_address_range, parse_addresses, parse_asm_bank_directive,
parse_asm_data_directive, parse_asm_keep_directive, parse_asm_nowarn_directive,
parse_asm_refs_directive, parse_asm_sub_fix_directive, parse_entry_header,
parse_instruction, read_skool, set_bytes)
parse_asm_bytes_directive, parse_asm_data_directive, parse_asm_keep_directive,
parse_asm_nowarn_directive, parse_asm_refs_directive, parse_asm_sub_fix_directive,
parse_entry_header, parse_instruction, read_skool, set_bytes)
from skoolkit.textutils import split_quoted, split_unquoted

Reference = namedtuple('Reference', 'entry address addr_str use_label')
Expand Down Expand Up @@ -260,11 +260,14 @@ def _parse_skool(self, skoolfile, asm_mode, min_address, max_address):
self.mode.apply_asm_directives(self.snapshot, instruction, map_entry, self._instructions, address_comments, removed)
self.ignores.clear()

# Set bytes in the snapshot if the instruction is DEF{B,M,S,W}
if address is not None:
assemble = self.utility.set_byte_values(instruction, self.mode.assemble)
if assemble:
data = set_bytes(self.snapshot, self._assembler, address, instruction.operation)
if instruction.bytes:
# Byte values have been specified by a @bytes directive
data = self.snapshot[address:address + len(instruction.bytes)] = instruction.bytes
else:
data = set_bytes(self.snapshot, self._assembler, address, instruction.operation)
if assemble > 1:
instruction.bytes = data

Expand Down Expand Up @@ -349,6 +352,9 @@ def _replace(self, text):
def _parse_asm_directive(self, directive, removed):
if directive.startswith('label='):
self.mode.label = directive[6:].rstrip()
elif directive.startswith('bytes='):
if self.mode.assemble:
self.mode.bvalues = parse_asm_bytes_directive(directive)
elif directive.startswith(('defb=', 'defs=', 'defw=')):
if self.mode.assemble:
self.mode.data.append(directive)
Expand Down Expand Up @@ -521,6 +527,7 @@ def reset(self):
self.refs = ((), ())
self.ignoreua = {'i': None, 'm': None}
self.org = None
self.bvalues = ()

def add_sub(self, directive, value):
weight = self.weights[directive]
Expand Down Expand Up @@ -567,6 +574,8 @@ def process_instruction(self, instruction, label, overwrite=False, removed=None)
return instruction.address + size

def apply_asm_directives(self, snapshot, instruction, map_entry, instructions, address_comments, removed):
if self.assemble > 1:
instruction.bytes = self.bvalues
instruction.keep = self.keep
instruction.refs, instruction.rrefs = self.refs

Expand Down
9 changes: 8 additions & 1 deletion skoolkit/skoolutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
from collections import namedtuple
import re

from skoolkit import CASE_LOWER, CASE_UPPER, ROM128, SkoolParsingError, parse_int, read_bin_file, wrap, z80
from skoolkit import (CASE_LOWER, CASE_UPPER, ROM128, SkoolParsingError,
get_int_param, parse_int, read_bin_file, wrap, z80)
from skoolkit.skoolmacro import ClosingBracketError, MacroParsingError, parse_brackets, parse_strings
from skoolkit.textutils import partition_unquoted

Expand Down Expand Up @@ -494,6 +495,12 @@ def parse_asm_block_directive(directive, stack):
return True
return False

def parse_asm_bytes_directive(directive):
try:
return tuple(get_int_param(b) for b in directive[6:].split(','))
except ValueError:
return ()

def parse_asm_data_directive(snapshot, address, directive, advance=True):
a, sep, values = directive[5:].rpartition(':')
if sep:
Expand Down
31 changes: 31 additions & 0 deletions sphinx/source/asm.rst
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,37 @@ For example::
JR NZ,32232 ;
@bfix+end

.. _bytes:

@bytes
^^^^^^
The ``@bytes`` directive specifies the byte values to which the next
instruction should assemble. ::

@bytes=value1[,value2...]

* ``value1``, ``value2`` etc. are the byte values

This directive is useful only for specifying an alternative set of opcodes for
an instruction that has two or more valid sets, such as 'LD HL,(nn)' (2A or
ED6B) and 'IM 1' (ED56 or ED76). It ensures that the memory snapshot
constructed by :ref:`skool2asm.py` or :ref:`skool2html.py` will contain the
correct byte values at the instruction's address.

For example::

@bytes=$ED,$4C
40000 NEG

This ``@bytes`` directive makes the NEG instruction that follows assemble to
ED4C (instead of the standard ED44).

+---------+---------+
| Version | Changes |
+=========+=========+
| 9.3 | New |
+---------+---------+

.. _defb:

@defb
Expand Down
2 changes: 2 additions & 0 deletions sphinx/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Changelog
* Added the ``Opcodes`` configuration parameter to
:ref:`sna2skool.py <sna2skool-conf>` (for specifying additional opcode
sequences to disassemble)
* Added the :ref:`bytes` directive (for specifying the byte values to which an
instruction should assemble)
* The ``--find``, ``--find-text`` and ``--find-tile`` options of
:ref:`snapinfo.py` now search all RAM banks in a 128K snapshot by default
* Added support for path ID replacement fields in the ``destDir`` parameter of
Expand Down
1 change: 1 addition & 0 deletions sphinx/source/parsing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ following:

* :ref:`assemble`
* :ref:`asm-bank`
* :ref:`bytes`
* :ref:`defb`
* :ref:`defs`
* :ref:`defw`
Expand Down
15 changes: 15 additions & 0 deletions tests/test_ctlparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -1089,6 +1089,21 @@ def test_bfix_directives(self):
}
self._test_asm_directives(ctl, exp_entry_directives, exp_instruction_directives)

def test_bytes_directives(self):
ctl = """
@ 50000 bytes=237,84
c 50000 Routine at 50000
@ 50002 bytes=$ED,$55
"""
exp_entry_directives = {
50000: []
}
exp_instruction_directives = {
50000: ['bytes=237,84'],
50002: ['bytes=$ED,$55']
}
self._test_asm_directives(ctl, exp_entry_directives, exp_instruction_directives)

def test_defb_directives(self):
ctl = """
@ 30000 defb=49152:13
Expand Down
19 changes: 19 additions & 0 deletions tests/test_skool2bin.py
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,25 @@ def test_bank_directive_with_nonexistent_skool_file(self):
self.run_skool2bin(f'--banks {skoolfile}')
self.assertEqual(cm.exception.args[0], 'nonexistent.skool: file not found')

def test_bytes_directives(self):
skool = """
@bytes=237,107,0,192
c32768 LD HL,(49152)
32772 INC HL
@bytes=$ED,$63,$02,$C0
32773 LD (49154),HL
"""
exp_data = [237, 107, 0, 192, 35, 0xED, 0x63, 0x02, 0xC0]
self._test_write(skool, 32768, exp_data)

def test_invalid_bytes_directive_is_ignored(self):
skool = """
@bytes=0,?
c40000 NEG
"""
exp_data = [237, 68]
self._test_write(skool, 40000, exp_data)

def test_data_directives_ignored(self):
skool = """
@defb=30001:1
Expand Down
48 changes: 48 additions & 0 deletions tests/test_skoolasm.py
Original file line number Diff line number Diff line change
Expand Up @@ -2029,6 +2029,54 @@ def test_entry_point_labels(self):
self.assertEqual(asm[3], 'START_0:')
self.assertEqual(asm[5], 'START_1:')

def test_bytes_directives(self):
skool = """
@start
@expand=#DEF(#BYTES(size) #FOR(#PC,#PC+$size-1,,1)(a,#PEEK(a)))
; Routine at 32768
@bytes=237,107,0,192
c32768 LD HL,(49152) ; #BYTES4
32772 INC HL ; #BYTES1
@bytes=$ED,$63,$02,$C0
32773 LD (49154),HL ; #BYTES4
"""
exp_asm = """
; Routine at 32768
LD HL,(49152) ; 237,107,0,192
INC HL ; 35
LD (49154),HL ; 237,99,2,192
"""
self._test_asm(skool, exp_asm)

def test_bytes_directive_with_assembly_disabled(self):
skool = """
@start
@expand=#DEF(#BYTES(size) #FOR(#PC,#PC+$size-1,,1)(a,#PEEK(a)))
@assemble=,1
; Routine at 32768
@bytes=237,107,0,192
c32768 LD HL,(49152) ; #BYTES4
"""
exp_asm = """
; Routine at 32768
LD HL,(49152) ; 0,0,0,0
"""
self._test_asm(skool, exp_asm)

def test_invalid_bytes_directive_is_ignored(self):
skool = """
@start
@expand=#DEF(#BYTES(size) #FOR(#PC,#PC+$size-1,,1)(a,#PEEK(a)))
; Routine at 32768
@bytes=237,?
c32768 NEG ; #BYTES2
"""
exp_asm = """
; Routine at 32768
NEG ; 237,68
"""
self._test_asm(skool, exp_asm)

def test_equ_directives(self):
skool = """
@start
Expand Down
16 changes: 16 additions & 0 deletions tests/test_skoolctl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,22 @@ def test_defw_crossing_64k_boundary(self):
"""
self._test_ctl(skool, exp_ctl)

def test_bytes_directives(self):
skool = """
; Routine
@bytes=237,107,0,192
c32768 LD HL,(49152)
@bytes=$ED,$63,$02,$C0
32772 LD (49154),HL
"""
exp_ctl = """
c 32768 Routine
@ 32768 bytes=237,107,0,192
@ 32772 bytes=$ED,$63,$02,$C0
i 32776
"""
self._test_ctl(skool, exp_ctl)

def test_ignoreua_directives(self):
skool = """
@ignoreua
Expand Down
Loading

0 comments on commit effedbb

Please sign in to comment.