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
from dataclasses import dataclass
import io
import itertools
import multiprocessing
import multiprocessing.pool
from pathlib import Path
@ -19,6 +18,7 @@ import re
import shlex
import sys
import time
import traceback
from typing import BinaryIO, Iterator
from ido_block_numbers import (
@ -501,32 +501,97 @@ def solve_bss_ordering(
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]):
with open(file, "r", encoding="utf-8") as f:
lines = f.readlines()
replace_lines: list[tuple[int, int, list[str]]] = []
for pragma in new_pragmas:
line = lines[pragma.line_number - 1]
if not line.startswith("#pragma increment_block_number "):
i = pragma.line_number - 1
if not lines[i].startswith("#pragma increment_block_number"):
raise FixBssException(
f"Expected #pragma increment_block_number on line {pragma.line_number}"
)
# Grab pragma argument and remove quotes
arg = line.strip()[len("#pragma increment_block_number ") + 1 : -1]
# list the pragma line and any continuation line
pragma_lines = [lines[i]]
while pragma_lines[-1].endswith("\\\n"):
i += 1
pragma_lines.append(lines[i])
amounts_by_version = {}
for part in arg.split():
version, amount_str = part.split(":")
amounts_by_version[version] = int(amount_str)
# concatenate all lines into one
pragma_string = "".join(s.replace("\\\n", "") for s in pragma_lines)
amounts_by_version[version_to_update] = pragma.amount
new_arg = " ".join(
f"{version}:{amount}" for version, amount in amounts_by_version.items()
amounts = parse_pragma(pragma_string)
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:
f.writelines(lines)
@ -600,9 +665,15 @@ def process_file_worker(*x):
try:
sys.stdout = fake_stdout
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}")
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:
sys.stdout = old_stdout
print()

View file

@ -11,10 +11,11 @@
import argparse
from pathlib import Path
import os
import re
import tempfile
import subprocess
import sys
import typing
def fail(message):
@ -22,36 +23,59 @@ def fail(message):
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')
# 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):
if line.startswith("#pragma increment_block_number "):
# Grab pragma argument and remove quotes
arg = line.strip()[len("#pragma increment_block_number ") + 1 : -1]
amount = 0
for part in arg.split():
kv = part.split(":")
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"
)
if not in_pragma_incblocknum and line.startswith(
"#pragma increment_block_number"
):
in_pragma_incblocknum = True
pragma_incblocknum_first_line_num = i
pragma_incblocknum_lines = []
# Always generate at least one struct so that fix_bss.py can know where the increment_block_number pragmas are
if amount == 0:
amount = 256
if in_pragma_incblocknum:
if line.endswith("\\\n"):
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
for j in range(amount):
output.write(f"struct increment_block_number_{i:05}_{j:03};\n")
output.write(f'#line {i + 1} "{filename}"\n')
# Always generate at least one struct,
# so that fix_bss.py can know where the increment_block_number pragmas are
if amount == 0:
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:
output.write(line)