Last active
May 10, 2024 06:34
-
-
Save Pinacolada64/3956e890d597a8a165077d02fe95ce61 to your computer and use it in GitHub Desktop.
experiment to create a macro system for c64list
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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