Skip to content

Commit

Permalink
Add support to #FOREACH for the 'POKEname' special variable
Browse files Browse the repository at this point in the history
  • Loading branch information
skoolkid committed Aug 23, 2024
1 parent 023ca25 commit 1eecabd
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 8 deletions.
8 changes: 7 additions & 1 deletion skoolkit/skoolasm.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2008-2023 Richard Dymond ([email protected])
# Copyright 2008-2024 Richard Dymond ([email protected])
#
# This file is part of SkoolKit.
#
Expand All @@ -14,6 +14,7 @@
# You should have received a copy of the GNU General Public License along with
# SkoolKit. If not, see <http://www.gnu.org/licenses/>.

from collections import defaultdict
import re

from skoolkit import (CASE_LOWER, skoolmacro, SkoolKitError, SkoolParsingError,
Expand Down Expand Up @@ -95,6 +96,7 @@ def __init__(self, parser, properties, templates, config):

self.snapshot = self.parser.snapshot
self._snapshots = [(self.snapshot, '')]
self.pokes = defaultdict(list)

self.list_parser = ListParser(properties.get('bullet', '*'))

Expand Down Expand Up @@ -227,6 +229,10 @@ def push_snapshot(self, name=''):
:param name: An optional name for the snapshot.
"""
self._snapshots.append((self.snapshot.copy(), name))
self.pokes[name].clear()

def save_pokes(self, addr, byte, length, step):
self.pokes[self._snapshots[-1][1]].append((addr, byte, length, step))

def expand_audio(self, text, index):
if self.handle_unsupported_macros:
Expand Down
5 changes: 5 additions & 0 deletions skoolkit/skoolhtml.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ def __init__(self, skool_parser, ref_parser, file_info=None, code_id=MAIN_CODE_I

self.snapshot = self.parser.snapshot
self._snapshots = [(self.snapshot, '')]
self.pokes = defaultdict(list)
self.asm_entry_dicts = {}
self.map_entry_dicts = {}
self.nonexistent_entry_dict = defaultdict(lambda: '', exists=0)
Expand Down Expand Up @@ -458,6 +459,10 @@ def push_snapshot(self, name=''):
:param name: An optional name for the snapshot.
"""
self._snapshots.append((self.snapshot.copy(), name))
self.pokes[name].clear()

def save_pokes(self, addr, byte, length, step):
self.pokes[self.get_snapshot_name()].append((addr, byte, length, step))

def get_page_ids(self):
return self.page_ids
Expand Down
18 changes: 16 additions & 2 deletions skoolkit/skoolmacro.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@ def get_macros(writer):
'#DEF': partial(parse_def, writer),
'#EVAL': partial(parse_eval, writer.fields, writer.case == CASE_LOWER),
'#FOR': partial(parse_for, writer.fields),
'#FOREACH': partial(parse_foreach, writer.parser),
'#FOREACH': partial(parse_foreach, writer),
'#FORMAT': partial(parse_format, writer.fields),
'#IF': partial(parse_if, writer.fields),
'#LET': partial(parse_let, writer),
Expand Down Expand Up @@ -912,7 +912,7 @@ def parse_for(fields, text, index, *cwd):
return end, html.escape(''.join(elements))
return end, ''.join(elements)

def parse_foreach(entry_holder, text, index, *cwd):
def parse_foreach(writer, text, index, *cwd):
# #FOREACH([v1,v2,...])(var,string[,sep,fsep])
try:
end, values = parse_strings(text, index)
Expand All @@ -922,6 +922,7 @@ def parse_foreach(entry_holder, text, index, *cwd):
end, (var, s, sep, fsep) = parse_strings(text, end, 4, ('', None))
except (NoParametersError, MissingParameterError) as e:
raise MacroParsingError("No variable name: {}".format(text[index:e.args[1]]))
entry_holder = writer.parser
if len(values) == 1:
value = values[0]
if value.startswith(('EREF', 'REF')):
Expand All @@ -940,6 +941,18 @@ def parse_foreach(entry_holder, text, index, *cwd):
elif value.startswith('ENTRY'):
types = value[5:]
values = [str(e.address) for e in entry_holder.memory_map if e.ctl != 'i' and (not types or e.ctl in types)]
elif value.startswith('POKE'):
name = value[4:]
values = []
for addr, byte, length, step in writer.pokes[name]:
if length == 1:
values.append(f'POKE {addr},{byte}')
else:
end = addr + (length - 1) * step
if step == 1:
values.append(f'FOR n={addr} TO {end}: POKE n,{byte}: NEXT n')
else:
values.append(f'FOR n={addr} TO {end} STEP {step}: POKE n,{byte}: NEXT n')
if not values:
return end, ''
if fsep is None:
Expand Down Expand Up @@ -1156,6 +1169,7 @@ def parse_pokes(writer, text, index, *cwd):
while end < index or (end < len(text) and text[end] == ';'):
end, addr, byte, length, step = parse_ints(text, end + 1, 4, (1, 1), fields=writer.fields)
writer.snapshot[addr:addr + length * step:step] = [byte] * length
writer.save_pokes(addr, byte, length, step)
return end, ''

def parse_pops(writer, text, index, *cwd):
Expand Down
2 changes: 2 additions & 0 deletions sphinx/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Changelog
configuration parameter values)
* Added support to :ref:`skool2bin.py` for padding the output with zeroes (as
specified by the ``PadLeft`` and ``PadRight`` configuration parameters)
* Added support to the :ref:`FOREACH` macro for the ``POKEname`` special
variable
* Fixed how the 'ADC A,*', 'SBC A,*', 'ADC HL,rr' and 'SBC HL,rr' instructions
affect the half-carry flag
* Fixed how 'BIT n,(IX/Y+d)' affects bits 3 and 5 of the flags in the C version
Expand Down
22 changes: 17 additions & 5 deletions sphinx/source/skool-macros.rst
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,8 @@ expands to a specific sequence of strings. The special variables are:
the memory map; if ``types`` is not given, every type is included
* ``EREFaddr`` - the addresses of the routines that jump to or call a given
instruction (at ``addr``)
* ``POKEname`` - the POKEs made by the :ref:`POKES` macro on the named snapshot
created by the :ref:`PUSHS` macro
* ``REFaddr`` - the addresses of the routines that jump to or call a given
routine (at ``addr``), or jump to or call any entry point within that routine

Expand All @@ -471,14 +473,24 @@ For example::
This instance of the ``#FOREACH`` macro expands to a list of the addresses of
the entries of type ``t`` (text).

The format of an item produced by ``POKEname`` depends on the values of the
``length`` and ``step`` parameters of the corresponding :ref:`POKES` macro. For
example:

* ``#POKES30000,1`` gives 'POKE 30000,1'
* ``#POKES30000,1,2`` gives 'FOR n=30000 TO 30001: POKE n,1: NEXT n'
* ``#POKES30000,1,2,3`` gives 'FOR n=30000 TO 30003 STEP 3: POKE n,1: NEXT n'

See :ref:`stringParameters` for details on alternative ways to supply the
``s1,s2,...`` and ``var,string[,sep,fsep]`` parameter strings.

+---------+---------+
| Version | Changes |
+=========+=========+
| 5.1 | New |
+---------+---------+
+---------+-----------------------------------------------------+
| Version | Changes |
+=========+=====================================================+
| 9.4 | Added support for the ``POKEname`` special variable |
+---------+-----------------------------------------------------+
| 5.1 | New |
+---------+-----------------------------------------------------+

.. _FORMAT:

Expand Down
64 changes: 64 additions & 0 deletions tests/macrotest.py
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,70 @@ def test_macro_foreach_with_ref_invalid(self):
self.assertEqual(writer.expand('#FOREACH(REFx)(n,n)'), 'REFx')
self.assertEqual(writer.expand('#FOREACH[REF(x)](n,n)'), 'REF(x)')

def test_macro_foreach_with_poke(self):
skool = """
@start
b30000 DEFB 1
"""
writer = self._get_writer(skool=skool)
writer.expand('#PUSHSfoo #POKES30000,2 #POPS')
self.assertEqual(writer.expand('#FOREACH(POKEfoo)(p,p)'), 'POKE 30000,2')

def test_macro_foreach_with_poke_length(self):
skool = """
@start
b30000 DEFB 1,1
"""
writer = self._get_writer(skool=skool)
writer.expand('#PUSHSbar #POKES30000,3,2 #POPS')
self.assertEqual(writer.expand('#FOREACH(POKEbar)(p,p)'), 'FOR n=30000 TO 30001: POKE n,3: NEXT n')

def test_macro_foreach_with_poke_length_and_step(self):
skool = """
@start
b30000 DEFB 1,0,1
"""
writer = self._get_writer(skool=skool)
writer.expand('#PUSHSbaz #POKES30000,4,2,2 #POPS')
self.assertEqual(writer.expand('#FOREACH(POKEbaz)(p,p)'), 'FOR n=30000 TO 30002 STEP 2: POKE n,4: NEXT n')

def test_macro_foreach_with_poke_multiple(self):
skool = """
@start
b30000 DEFB 1,1,1
"""
writer = self._get_writer(skool=skool)
writer.expand('#PUSHSqux #POKES30000,5;30001,6;30002,7 #POPS')
self.assertEqual(writer.expand('#FOREACH(POKEqux)(p,[p])'), '[POKE 30000,5][POKE 30001,6][POKE 30002,7]')

def test_macro_foreach_with_poke_none(self):
skool = """
@start
b30000 DEFB 1
"""
writer = self._get_writer(skool=skool)
writer.expand('#PUSHSxyzzy #POKES30000,2 #POPS')
self.assertEqual(writer.expand('#FOREACH(POKEnope)(p,p)'), '')

def test_macro_foreach_with_poke_nameless_snapshot(self):
skool = """
@start
b30000 DEFB 1
"""
writer = self._get_writer(skool=skool)
writer.expand('#PUSHS #POKES30000,8 #POPS')
self.assertEqual(writer.expand('#FOREACH(POKE)(n,n)'), 'POKE 30000,8')

def test_macro_foreach_with_poke_two_snapshots(self):
skool = """
@start
b30000 DEFB 1
"""
writer = self._get_writer(skool=skool)
writer.expand('#PUSHSthud #POKES30000,9 #POPS')
writer.expand('#PUSHSthud #POKES30000,10 #POPS')
self.assertEqual(writer.expand('#FOREACH(POKEthud)(a,a)'), 'POKE 30000,10')

def test_macro_foreach_invalid(self):
writer = self._get_writer()
prefix = ERROR_PREFIX.format('FOREACH')
Expand Down

0 comments on commit 1eecabd

Please sign in to comment.