1 changed files with 0 additions and 353 deletions
@ -1,353 +0,0 @@
|
||||
#!/usr/bin/env python |
||||
import argparse |
||||
import enum |
||||
import re |
||||
import pathlib |
||||
from typing import NamedTuple |
||||
|
||||
|
||||
def Main(): |
||||
root = pathlib.Path(__file__).resolve().parent.parent |
||||
parser = argparse.ArgumentParser() |
||||
parser.add_argument( |
||||
"files", |
||||
nargs="*", |
||||
default=[ |
||||
root.joinpath("Source/effects.cpp"), |
||||
root.joinpath("Source/itemdat.cpp"), |
||||
root.joinpath("Source/misdat.cpp"), |
||||
root.joinpath("Source/monstdat.cpp"), |
||||
root.joinpath("Source/objdat.cpp"), |
||||
root.joinpath("Source/spelldat.cpp"), |
||||
], |
||||
) |
||||
args = parser.parse_args() |
||||
|
||||
for file in args.files: |
||||
Process(file) |
||||
|
||||
|
||||
class LineState(enum.Enum): |
||||
NONE = 1 |
||||
IN_TABLE = 2 |
||||
|
||||
|
||||
class ColumnAlign(enum.Enum): |
||||
LEFT = 1 |
||||
RIGHT = 2 |
||||
|
||||
|
||||
class ColumnsState: |
||||
widths: list[int] |
||||
aligns: list[ColumnAlign] |
||||
has_header: bool |
||||
first_row: list[str] |
||||
|
||||
def __init__(self) -> None: |
||||
self.widths = [] |
||||
self.aligns = [] |
||||
self.has_header = False |
||||
self.first_row = [] |
||||
|
||||
|
||||
def Process(path: str): |
||||
with open(path, "r", encoding="utf-8", newline="\r\n") as f: |
||||
input = f.read().splitlines() |
||||
|
||||
columns_state = ColumnsState() |
||||
output_lines = [] |
||||
state = LineState.NONE |
||||
begin = 0 |
||||
for i in range(len(input)): |
||||
prev_state = state |
||||
state = ProcessLine(input[i], state, columns_state) |
||||
if prev_state != state: |
||||
columns_state.has_header = False |
||||
for j in range(begin, i): |
||||
output_line = FormatLine(input[j], prev_state, columns_state) |
||||
output_lines.append(output_line) |
||||
columns_state = ColumnsState() |
||||
begin = i |
||||
columns_state.has_header = False |
||||
for j in range(begin, len(input)): |
||||
output_line = FormatLine(input[j], state, columns_state) |
||||
output_lines.append(output_line) |
||||
|
||||
with open(path, "w", encoding="utf-8", newline="") as f: |
||||
f.writelines(f"{line}\r\n" for line in output_lines) |
||||
|
||||
|
||||
class CharState: |
||||
parentheses: list[str] |
||||
quotes: list[str] |
||||
backslash_escape: bool |
||||
pushed_paren: str |
||||
prev_char: str |
||||
in_comment: bool |
||||
|
||||
def __init__(self) -> None: |
||||
self.parentheses = [] |
||||
self.quotes = [] |
||||
self.backslash_escape = False |
||||
self.pushed_paren = "" |
||||
self.prev_char = "" |
||||
self.in_comment = False |
||||
|
||||
|
||||
_PARENTHESES_MAP = {")": "(", "}": "{", "]": "["} |
||||
_OPEN_PARENTHESES = _PARENTHESES_MAP.values() |
||||
_CLOSE_PARENTHESES = _PARENTHESES_MAP.keys() |
||||
|
||||
|
||||
def UpdateCharState(c: str, state: CharState): |
||||
prev_char = state.prev_char |
||||
state.prev_char = c |
||||
state.pushed_paren = "" |
||||
if state.in_comment: |
||||
if prev_char == "*" and c == "/": |
||||
state.in_comment = False |
||||
return |
||||
if prev_char == "/" and c == "*": |
||||
state.in_comment = True |
||||
return |
||||
if state.backslash_escape: |
||||
state.backslash_escape = False |
||||
return |
||||
if c == "\\": |
||||
state.backslash_escape = True |
||||
elif c == '"' or c == "'": |
||||
if not state.quotes: |
||||
state.quotes.append(c) |
||||
elif state.quotes[-1] == c: |
||||
state.quotes.pop() |
||||
elif not state.quotes: |
||||
if c in _OPEN_PARENTHESES: |
||||
state.parentheses.append(c) |
||||
state.pushed_paren = c |
||||
elif c in _CLOSE_PARENTHESES: |
||||
if state.parentheses and state.parentheses[-1] == _PARENTHESES_MAP.get(c): |
||||
state.parentheses.pop() |
||||
else: |
||||
raise RuntimeError( |
||||
f"Mismatched parenthesis. Stack: {state.parentheses}. Value: '{c}'" |
||||
) |
||||
|
||||
|
||||
_SKIP_LINE_RE = re.compile(r"^\s*(//|\})") |
||||
_HEADER_COMMENT_RE = re.compile(r"^\s*//(?! TRANSLATORS)") |
||||
_HEADER_CONTENTS_RE = re.compile(r"^\s*//\s*(.*)$") |
||||
|
||||
|
||||
class Row(NamedTuple): |
||||
header: bool |
||||
leading_comment: bool |
||||
columns: list[str] |
||||
|
||||
|
||||
def ParseHeader(line: str) -> list[str]: |
||||
parens = [] |
||||
columns = [] |
||||
begin = end = 0 |
||||
leading_spaces = True |
||||
for i in range(len(line)): |
||||
c = line[i] |
||||
if c == "{": |
||||
if not parens: |
||||
if end > begin: |
||||
columns.append(line[begin:end]) |
||||
begin = end = i |
||||
parens.append(c) |
||||
continue |
||||
elif c == "}": |
||||
if not parens or parens[-1] != "{": |
||||
raise RuntimeError("Mismatched paretheses") |
||||
parens.pop() |
||||
if not parens: |
||||
if i >= begin: |
||||
columns.append(line[begin : i + 1]) |
||||
begin = end = i |
||||
elif parens: |
||||
end = i + 1 |
||||
else: |
||||
if c == " ": |
||||
if not leading_spaces: |
||||
if end > begin: |
||||
columns.append(line[begin:end]) |
||||
leading_spaces = True |
||||
begin = end = i |
||||
else: |
||||
if leading_spaces: |
||||
begin = i |
||||
leading_spaces = False |
||||
else: |
||||
end = i + 1 |
||||
if end > begin: |
||||
columns.append(line[begin:end]) |
||||
return columns |
||||
|
||||
|
||||
def ParseRow(line: str, column_state: ColumnsState) -> Row: |
||||
if line.endswith("// clang-format off"): |
||||
return Row(header=False, leading_comment=False, columns=[]) |
||||
if not column_state.has_header and _HEADER_COMMENT_RE.match(line): |
||||
header_columns = ParseHeader(_HEADER_CONTENTS_RE.match(line).group(1)) |
||||
if len(header_columns) > 1: |
||||
column_state.has_header = True |
||||
return Row(header=True, leading_comment=False, columns=header_columns) |
||||
|
||||
if _SKIP_LINE_RE.match(line): |
||||
return Row(header=False, leading_comment=False, columns=[]) |
||||
|
||||
state = CharState() |
||||
leading_comment = False |
||||
column_begin = 0 |
||||
column_end = 0 |
||||
leading_spaces = True |
||||
columns = [] |
||||
for i in range(len(line)): |
||||
c = line[i] |
||||
try: |
||||
UpdateCharState(c, state) |
||||
except RuntimeError as e: |
||||
raise RuntimeError(f" in:\n{line}") from e |
||||
if (state.parentheses and state.parentheses != ["{"]) or state.quotes: |
||||
if leading_spaces: |
||||
leading_spaces = False |
||||
column_begin = column_end = i |
||||
else: |
||||
column_end = i |
||||
continue |
||||
|
||||
# Top-level "{": |
||||
if state.pushed_paren == "{" and state.parentheses == ["{"]: |
||||
column = line[column_begin:column_end] |
||||
if column: |
||||
if column.startswith("/*"): |
||||
leading_comment = True |
||||
columns.append(column) |
||||
column_begin = column_end + 2 |
||||
column_end = column_begin |
||||
leading_spaces = True |
||||
continue |
||||
|
||||
# Top-level "}": |
||||
if ( |
||||
c == "}" |
||||
and not state.in_comment |
||||
and not state.quotes |
||||
and not state.parentheses |
||||
): |
||||
columns.append(line[column_begin:column_end]) |
||||
break |
||||
|
||||
if state.in_comment: |
||||
if leading_spaces: |
||||
leading_spaces = False |
||||
column_begin = i |
||||
column_end = i + 1 |
||||
elif c == " " or c == "\t": |
||||
if leading_spaces: |
||||
column_begin += 1 |
||||
elif c == ",": |
||||
columns.append(line[column_begin:column_end] + c) |
||||
column_begin = column_end + 1 |
||||
column_end = column_begin |
||||
leading_spaces = True |
||||
elif leading_spaces: |
||||
leading_spaces = False |
||||
column_begin = i |
||||
column_end = i + 1 |
||||
else: |
||||
column_end = i + 1 |
||||
|
||||
return Row(header=False, leading_comment=leading_comment, columns=columns) |
||||
|
||||
|
||||
_RIGHT_ALIGN_RE = re.compile(r"^-?\d") |
||||
|
||||
|
||||
def CompareRows(a: list[str], b: list[str]): |
||||
a_width = max(len(x) for x in a) + 2 |
||||
b_width = max(len(x) for x in b) + 2 |
||||
shared_len = min(len(a), len(b)) |
||||
result = [] |
||||
for i in range(shared_len): |
||||
result.append(f"{f'[{a[i]}]'.ljust(a_width)} | {f'[{b[i]}]'.ljust(b_width)}") |
||||
if len(a) > len(b): |
||||
for i in range(shared_len, len(a)): |
||||
result.append(f"{f'[{a[i]}]'.ljust(a_width)} |") |
||||
else: |
||||
for i in range(shared_len, len(b)): |
||||
result.append(f"{''.ljust(a_width)} | {f'[{b[i]}]'.ljust(b_width)}") |
||||
return "\n".join(result) |
||||
|
||||
|
||||
def ProcessLine(line: str, line_state: LineState, state: ColumnsState) -> LineState: |
||||
if line_state == LineState.IN_TABLE: |
||||
if line.endswith("// clang-format on"): |
||||
return LineState.NONE |
||||
row = ParseRow(line, state) |
||||
if len(row.columns) < 2: |
||||
return line_state |
||||
if not state.widths: |
||||
state.first_row = list(row.columns) |
||||
for column in row.columns: |
||||
state.widths.append(len(column) + 1) |
||||
state.aligns.append(ColumnAlign.RIGHT) |
||||
return line_state |
||||
if len(row.columns) != len(state.widths): |
||||
raise RuntimeError( |
||||
f"Expected {len(state.widths)} columns, got {len(row.columns)}.\n" |
||||
+ CompareRows(state.first_row, row.columns) |
||||
) |
||||
for i in range(len(row.columns)): |
||||
column = row.columns[i] |
||||
state.widths[i] = max(len(column), state.widths[i]) |
||||
if column and not _RIGHT_ALIGN_RE.match(column): |
||||
state.aligns[i] = ColumnAlign.LEFT |
||||
elif line.endswith("// clang-format off"): |
||||
return LineState.IN_TABLE |
||||
return line_state |
||||
|
||||
|
||||
def FormatColumn(column: str, align: ColumnAlign, width: int): |
||||
return column.ljust(width) if align == ColumnAlign.LEFT else column.rjust(width) |
||||
|
||||
|
||||
def FormatLine(line: str, line_state: LineState, state: ColumnsState) -> str: |
||||
if line_state == LineState.NONE: |
||||
return line |
||||
row = ParseRow(line, state) |
||||
if len(row.columns) < 2: |
||||
return line |
||||
|
||||
if row.header: |
||||
return ( |
||||
"// " |
||||
+ " ".join( |
||||
FormatColumn(column.rstrip(), align, width) |
||||
for column, width, align in zip( |
||||
row.columns, [state.widths[0] - 1, *state.widths[1:]], state.aligns |
||||
) |
||||
).rstrip() |
||||
) |
||||
|
||||
result = [] |
||||
if row.leading_comment: |
||||
result.append(FormatColumn(row.columns[0], state.aligns[0], state.widths[0])) |
||||
result.append("{") |
||||
for column, width, align in zip( |
||||
row.columns[1:], state.widths[1:], state.aligns[1:] |
||||
): |
||||
result.append(FormatColumn(column, align, width)) |
||||
result.append("},") |
||||
return " ".join(result) |
||||
|
||||
result.append("{") |
||||
for column, width, align in zip(row.columns, state.widths, state.aligns): |
||||
result.append(FormatColumn(column, align, width)) |
||||
result.append("},") |
||||
return " ".join(result) |
||||
|
||||
|
||||
Main() |
||||
Loading…
Reference in new issue