mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 13:33:34 -04:00
81 lines
3.6 KiB
Python
81 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
|
"""macro_draft.py — materialize a `DEFINE_func_XXXX()` macro body as a compilable draft .c
|
|
|
|
WHY THIS EXISTS (Phase 30 S47)
|
|
==============================
|
|
`conform_decls.py` needs a **byte-true DEFINITION** to conform the fleet's declarations to, and it
|
|
refuses (correctly) when handed a file that holds only a declaration. But for a DEDUPED function the
|
|
definition does not live in any .c file at all — it lives inside a `#define DEFINE_func_X() \\ ...`
|
|
macro in `src/shared/engine_core.h`, where two things defeat every definition parser:
|
|
|
|
* every line ends in a backslash continuation, and
|
|
* the definition line is INDENTED, so `^([A-Za-z_]...)` never matches.
|
|
|
|
So the single largest class of `conflicting types for func_X` — a deduped callee whose macro-local
|
|
`extern` disagrees with the fleet's — was unreachable by the tool built to fix exactly that class.
|
|
This bridges the two: it emits the macro body verbatim (dedented, continuations stripped) so the
|
|
existing conformer can read the signature it already knows how to read.
|
|
|
|
VERBATIM IS THE POINT. The draft is evidence, not a rewrite: nothing is reformatted, no types are
|
|
"cleaned up". If this file ever starts editing the body it stops being byte-truth (R33/R35).
|
|
|
|
Usage:
|
|
tools/macro_draft.py --fn func_80128ED8 [--out .run/draft.c] # default: .run/macro_draft_<fn>.c
|
|
"""
|
|
import argparse, os, re, sys, textwrap
|
|
|
|
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
HDR = os.path.join(REPO, "src", "shared", "engine_core.h")
|
|
|
|
|
|
def extract(fn, header=HDR):
|
|
"""(body_text, first_line, last_line) of DEFINE_<fn>()'s macro body, 1-indexed; None if absent."""
|
|
lines = open(header, errors="replace").read().split("\n")
|
|
for i, l in enumerate(lines):
|
|
if re.match(rf"\s*#define\s+DEFINE_{re.escape(fn)}\(\)", l):
|
|
j = i
|
|
while j < len(lines) and lines[j].rstrip().endswith("\\"):
|
|
j += 1
|
|
raw = lines[i:j + 1]
|
|
# strip the trailing backslash continuation from every line, drop the #define line
|
|
body = [x.rstrip()[:-1].rstrip() if x.rstrip().endswith("\\") else x for x in raw][1:]
|
|
return textwrap.dedent("\n".join(body)), i + 1, j + 1
|
|
return None
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description=__doc__,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
ap.add_argument("--fn", required=True)
|
|
ap.add_argument("--out")
|
|
ap.add_argument("--header", default=HDR)
|
|
a = ap.parse_args()
|
|
|
|
got = extract(a.fn, a.header)
|
|
if not got:
|
|
print(f"macro_draft: no DEFINE_{a.fn}() in {a.header}", file=sys.stderr)
|
|
return 2
|
|
body, lo, hi = got
|
|
|
|
# R32: prove the emitted text actually contains a DEFINITION of the requested function, at
|
|
# column 0, where a definition parser can see it. A dedent that leaves it indented is a silent
|
|
# no-op that would surface later as "no DEFINITION" from the conformer.
|
|
if not re.search(rf"^[A-Za-z_][\w \t\*]*\b{re.escape(a.fn)}\s*\(", body, re.M):
|
|
print(f"macro_draft: DEFINE_{a.fn}() body carries no column-0 definition of {a.fn} "
|
|
f"(header lines {lo}-{hi}) — refusing to emit a draft that cannot be parsed",
|
|
file=sys.stderr)
|
|
return 2
|
|
|
|
out = a.out or os.path.join(REPO, ".run", f"macro_draft_{a.fn}.c")
|
|
os.makedirs(os.path.dirname(out), exist_ok=True)
|
|
with open(out, "w") as f:
|
|
f.write(f'/* verbatim body of DEFINE_{a.fn}() — {a.header} lines {lo}-{hi} */\n')
|
|
f.write('#include "engine_types.h"\n')
|
|
f.write(body.rstrip() + "\n")
|
|
print(out)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|