1
0
Fork 0
mirror of https://github.com/zeldaret/oot.git synced 2025-02-03 18:14:26 +00:00

Handle multiline #pragma increment_block_number (#2023)

* Handle multiline #pragma increment_block_number

* fix: align all continuation characters vertically

* simplify continuation line character alignment

* Update tools/fix_bss.py

Co-authored-by: cadmic <cadmic24@gmail.com>

---------

Co-authored-by: cadmic <cadmic24@gmail.com>
This commit is contained in:
Dragorn421 2024-08-10 02:13:59 +02:00 committed by GitHub
parent b2d57f685d
commit cd2264f018
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 137 additions and 42 deletions

View file

@ -11,7 +11,6 @@ from collections import Counter
import colorama import colorama
from dataclasses import dataclass from dataclasses import dataclass
import io import io
import itertools
import multiprocessing import multiprocessing
import multiprocessing.pool import multiprocessing.pool
from pathlib import Path from pathlib import Path
@ -19,6 +18,7 @@ import re
import shlex import shlex
import sys import sys
import time import time
import traceback
from typing import BinaryIO, Iterator from typing import BinaryIO, Iterator
from ido_block_numbers import ( from ido_block_numbers import (
@ -501,32 +501,97 @@ def solve_bss_ordering(
raise FixBssException("Could not find any solutions") raise FixBssException("Could not find any solutions")
# Parses #pragma increment_block_number (with line continuations already removed)
def parse_pragma(pragma_string: str) -> dict[str, int]:
amounts = {}
for part in pragma_string.replace('"', "").split()[2:]:
kv = part.split(":")
if len(kv) != 2:
raise FixBssException(
"#pragma increment_block_number"
f' arguments must be version:amount pairs, not "{part}"'
)
try:
amount = int(kv[1])
except ValueError:
raise FixBssException(
"#pragma increment_block_number"
f' amount must be an integer, not "{kv[1]}" (in "{part}")'
)
amounts[kv[0]] = amount
return amounts
# Formats #pragma increment_block_number as a list of lines
def format_pragma(amounts: dict[str, int], max_line_length: int) -> list[str]:
lines = []
pragma_start = "#pragma increment_block_number "
current_line = pragma_start + '"'
first = True
for version, amount in sorted(amounts.items()):
part = f"{version}:{amount}"
if len(current_line) + len(part) + len('" \\') > max_line_length:
lines.append(current_line + '" ')
current_line = " " * len(pragma_start) + '"'
first = True
if not first:
current_line += " "
current_line += part
first = False
lines.append(current_line + '"\n')
if len(lines) >= 2:
# add and align vertically all continuation \ characters
n_align = max(map(len, lines[:-1]))
for i in range(len(lines) - 1):
lines[i] = f"{lines[i]:{n_align}}\\\n"
return lines
def update_source_file(version_to_update: str, file: Path, new_pragmas: list[Pragma]): def update_source_file(version_to_update: str, file: Path, new_pragmas: list[Pragma]):
with open(file, "r", encoding="utf-8") as f: with open(file, "r", encoding="utf-8") as f:
lines = f.readlines() lines = f.readlines()
replace_lines: list[tuple[int, int, list[str]]] = []
for pragma in new_pragmas: for pragma in new_pragmas:
line = lines[pragma.line_number - 1] i = pragma.line_number - 1
if not line.startswith("#pragma increment_block_number "): if not lines[i].startswith("#pragma increment_block_number"):
raise FixBssException( raise FixBssException(
f"Expected #pragma increment_block_number on line {pragma.line_number}" f"Expected #pragma increment_block_number on line {pragma.line_number}"
) )
# Grab pragma argument and remove quotes # list the pragma line and any continuation line
arg = line.strip()[len("#pragma increment_block_number ") + 1 : -1] pragma_lines = [lines[i]]
while pragma_lines[-1].endswith("\\\n"):
i += 1
pragma_lines.append(lines[i])
amounts_by_version = {} # concatenate all lines into one
for part in arg.split(): pragma_string = "".join(s.replace("\\\n", "") for s in pragma_lines)
version, amount_str = part.split(":")
amounts_by_version[version] = int(amount_str)
amounts_by_version[version_to_update] = pragma.amount amounts = parse_pragma(pragma_string)
new_arg = " ".join(
f"{version}:{amount}" for version, amount in amounts_by_version.items() amounts[version_to_update] = pragma.amount
column_limit = 120 # matches .clang-format's ColumnLimit
new_pragma_lines = format_pragma(amounts, column_limit)
replace_lines.append(
(
pragma.line_number - 1,
pragma.line_number - 1 + len(pragma_lines),
new_pragma_lines,
)
) )
new_line = f'#pragma increment_block_number "{new_arg}"\n'
lines[pragma.line_number - 1] = new_line # Replace the pragma lines starting from the end of the file, so the line numbers
# for pragmas earlier in the file stay accurate.
replace_lines.sort(key=lambda it: it[0], reverse=True)
for start, end, new_pragma_lines in replace_lines:
del lines[start:end]
lines[start:start] = new_pragma_lines
with open(file, "w", encoding="utf-8") as f: with open(file, "w", encoding="utf-8") as f:
f.writelines(lines) f.writelines(lines)
@ -600,9 +665,15 @@ def process_file_worker(*x):
try: try:
sys.stdout = fake_stdout sys.stdout = fake_stdout
process_file(*x) process_file(*x)
except Exception as e: except FixBssException as e:
# exception with a message for the user
print(f"{colorama.Fore.RED}Error: {str(e)}{colorama.Fore.RESET}") print(f"{colorama.Fore.RED}Error: {str(e)}{colorama.Fore.RESET}")
raise raise
except Exception as e:
# "unexpected" exception, also print a trace for devs
print(f"{colorama.Fore.RED}Error: {str(e)}{colorama.Fore.RESET}")
traceback.print_exc(file=sys.stdout)
raise
finally: finally:
sys.stdout = old_stdout sys.stdout = old_stdout
print() print()

View file

@ -11,10 +11,11 @@
import argparse import argparse
from pathlib import Path from pathlib import Path
import os import re
import tempfile import tempfile
import subprocess import subprocess
import sys import sys
import typing
def fail(message): def fail(message):
@ -22,36 +23,59 @@ def fail(message):
sys.exit(1) sys.exit(1)
def process_file(version, filename, input, output): def process_file(
version: str,
filename: str,
input: typing.TextIO,
output: typing.TextIO,
):
output.write(f'#line 1 "{filename}"\n') output.write(f'#line 1 "{filename}"\n')
# whether the current line follows a #pragma increment_block_number,
# including continuation lines (lines after a \-ending line)
in_pragma_incblocknum = False
# the line where the #pragma increment_block_number is
pragma_incblocknum_first_line_num = None
# all the lines from the #pragma increment_block_number line to the last
# continuation line, as a list[str]
pragma_incblocknum_lines = None
for i, line in enumerate(input, start=1): for i, line in enumerate(input, start=1):
if line.startswith("#pragma increment_block_number "): if not in_pragma_incblocknum and line.startswith(
# Grab pragma argument and remove quotes "#pragma increment_block_number"
arg = line.strip()[len("#pragma increment_block_number ") + 1 : -1] ):
amount = 0 in_pragma_incblocknum = True
for part in arg.split(): pragma_incblocknum_first_line_num = i
kv = part.split(":") pragma_incblocknum_lines = []
if len(kv) != 2:
fail(
f"{filename}:{i}: increment_block_number must be followed by a list of version:amount pairs"
)
if kv[0] != version:
continue
try:
amount = int(kv[1])
except ValueError:
fail(
f"{filename}:{i}: increment_block_number amount must be an integer"
)
# Always generate at least one struct so that fix_bss.py can know where the increment_block_number pragmas are if in_pragma_incblocknum:
if amount == 0: if line.endswith("\\\n"):
amount = 256 pragma_incblocknum_lines.append(line)
else:
in_pragma_incblocknum = False
pragma_incblocknum_lines.append(line)
amount = 0
for s in pragma_incblocknum_lines:
# Note if we had two versions like "abc-def-version" and "def-version"
# then this code would find either given "def-version", but
# thankfully we don't have such nested version names.
m = re.search(rf"{version}:(\d+)\b", s)
if m:
amount = int(m.group(1))
break
# Write fake structs for BSS ordering # Always generate at least one struct,
for j in range(amount): # so that fix_bss.py can know where the increment_block_number pragmas are
output.write(f"struct increment_block_number_{i:05}_{j:03};\n") if amount == 0:
output.write(f'#line {i + 1} "{filename}"\n') amount = 256
# Write fake structs for BSS ordering
# pragma_incblocknum_first_line_num is used for symbol uniqueness, and
# also by fix_bss.py to locate the pragma these symbols originate from.
for j in range(amount):
output.write(
"struct increment_block_number_"
f"{pragma_incblocknum_first_line_num:05}_{j:03};\n"
)
output.write(f'#line {i + 1} "{filename}"\n')
else: else:
output.write(line) output.write(line)