Skip to content

Instantly share code, notes, and snippets.

@Pinacolada64
Last active May 10, 2024 06:34
Show Gist options
  • Save Pinacolada64/3956e890d597a8a165077d02fe95ce61 to your computer and use it in GitHub Desktop.
Save Pinacolada64/3956e890d597a8a165077d02fe95ce61 to your computer and use it in GitHub Desktop.
experiment to create a macro system for c64list
import re
import logging
import doctest
import tempfile
def process_macros(code: list):
"""
This function processes macro definitions and stores them in a dictionary.
:param code: A list of lines containing code and macro definitions.
:returns: A dictionary containing processed macros, where the key is the macro name
and any parameters the macro is required to have, and the value is a list of code lines for that macro.
:returns: A list of non-macro code
>>> code = ['{def:macro border @1}',
... 'lda @1',
... 'sta $d020',
... '{endmacro}'
... '{macro: border #5}',
... 'sta $d021']
>>> process_macros(code=code)
{'border @1': ['lda @1', 'sta $d020']}
['{macro: border #5}', 'sta $d021']
"""
macros = {}
non_macro_code = []
for line_num, line in enumerate(code, start=1):
logging.info(f"{line_num:3} {line}")
try:
current_macro = None # Track currently being defined macro
# Check for macro definition or end
if line.startswith('{def:macro '):
macro_body = []
logging.info(f'found macro definition: {line=}')
macro_definition = strip_surrounding(line, left="{def:macro ", right="}")
# flag that we're processing a macro:
current_macro = macro_definition
logging.info(f'after: {macro_definition=}')
# Extract macro parameters ("@" followed by a digit)
params = [param for param in macro_definition.split()[1:]
if param.startswith("@") and param[1].isdigit()]
logging.info(f"{params=}. {len(params)=}")
# collect following macro body lines until '{endmacro}':
for next_line in code[line_num:]: # Start from the next line
if next_line.strip() != '{endmacro}':
line_num += 1
macro_body.append(next_line)
logging.info(f'{line_num=} {macro_body=}')
else:
line_num += 1
logging.info(f"Found 'endmacro' at {line_num=}")
# line_num += len(macro_body) # Update line counter
macros.update({macro_definition: macro_body})
current_macro = None
break # Exit loop on finding endmacro
if current_macro:
raise SyntaxError(f"Line {line_num}: Unterminated macro definition")
else:
# not in a macro definition, collect these lines for non-macro code
non_macro_code.append(line)
except Exception as e:
raise SyntaxError(f"Line {line_num}: {e}")
# temp_file.write(processed_line + '\n') # Write to temporary file
print(f"macros: {macros}")
print(f"non-macros: {non_macro_code}")
return macros, non_macro_code
def strip_surrounding(line: str, left: str, right: str) -> str:
"""
Strips characters from the left and right sides of a string based on provided delimiters.
:param line: The string to be stripped.
:param left: The string to remove from the left side.
:param right: The string to remove from the right side.
:return: A new string with the characters stripped.
>>> test = "{def:macro blah @1}"
>>> strip_surrounding(test, "{def:macro ", "}")
'blah @1'
"""
return line.lstrip(left).rstrip(right)
def expand_macros(non_macro_code: list, macros: dict) -> list:
"""
Replaces macros in a line of code with their corresponding definitions.
:param non_macro_code: list of lines of code
:param macros: A dictionary containing defined macros.
:return: A new list with macros replaced.
"""
new_lines = []
for line_num, code_line in enumerate(non_macro_code):
if code_line.startswith("{macro: "):
# Replace macro call with its definition:
macro_name = strip_surrounding(code_line, "{macro: ", "}")
if macro_name in macros.keys():
# TODO: check that # of parameters match between macro definition and macro call
new_lines.append([macro_line for macro_line in macro[macro_name]])
else:
# macro name not in dict
pass
else:
new_lines.append(code_line)
print("Code replaced with macro definitions:")
for line_num, code in enumerate(new_lines):
print(f"{line_num:3} {code}")
return new_lines
if __name__ == '__main__':
# initialize logging:
logging.basicConfig(level="DEBUG")
# initialize doctest
doctest.testmod(verbose=True)
code = ['{def:macro border @1}',
'lda #@1',
'sta $d020',
'{endmacro}',
'{def:macro background @1}',
'lda #@1',
'sta $d021',
'{endmacro}',
'{def:macro loop @1}',
'ldy #@1',
'dey',
'bne *-2',
'{endmacro}',
'{def:macro nested_loop @1 @2}',
'ldx #@2',
'ldy #@1',
'dey',
'bne *-2',
'dex',
'bne *-8',
'{endmacro}',
'{macro: border #1}',
'{macro: loop #$ff}',
'{macro: nested_loop #$de #$ad}'
]
macros, non_macro_code = process_macros(code)
print(expand_macros(non_macro_code, macros))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment