mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-29 15:18:24 -04:00
fix(phase-30 S50): macro seeds carry only the DEFINITION, not the macro's decl layer
Measured on the 489-draft run: inline seeds banked 145/213 (68%), macro seeds 77/276 (28%),
and the macro failures were decl collisions to a fault — 'parse error before *', 'too few
arguments to function'. Cause: a de-macroized DEFINE_ block is decls + definition, and
pasting it wholesale reintroduces the exact decl-agreement failure the minimal-preamble
design exists to avoid. func_8016AB6C's block is 1,891 lines of which 108 are the function.
- seed_body() now returns the DEFINITION as the body and keeps the macro block as the DECL
SOURCE, so only the externs the definition actually references are emitted.
- aprop_symfix.body_in_text(): text-based sibling of body_text (R33 — one implementation).
Indented-definition fallback for macro blocks, confirmed by a '{' with no ';' before it,
so an indented CALL can never be mistaken for a definition.
This commit is contained in:
@@ -67,9 +67,14 @@ def seed_body(sb, seed_name, header="src/shared/engine_core.h"):
|
||||
if sb["kind"] == "macro":
|
||||
h = seed_file_text(sb["path"] or header)
|
||||
for nm in (sb.get("name"), seed_name):
|
||||
b = macro_body(h, nm) if nm else None
|
||||
if b:
|
||||
return b, h, "macro"
|
||||
block = macro_body(h, nm) if nm else None
|
||||
if block:
|
||||
# The DEFINITION only — the macro's own extern block becomes the DECL SOURCE, not
|
||||
# part of the draft. Pasting it wholesale reintroduces the exact decl-agreement
|
||||
# failure the minimal-preamble design exists to avoid: measured, macro seeds banked
|
||||
# 77/276 (28%) that way against inline's 145/213 (68%), and the macro failures were
|
||||
# `parse error before '*'` / `too few arguments` — decl collisions, every one.
|
||||
return ASF.body_in_text(block, nm) or block, block, "macro"
|
||||
return None, "", "macro"
|
||||
for nm in (sb.get("name"), seed_name):
|
||||
b = ASF.body_text(sb["path"], nm) if nm else None
|
||||
@@ -123,8 +128,13 @@ def build_draft(body, seed_name, member_name, renames, seed_text, dest_text,
|
||||
pat = re.compile(r'\b(' + '|'.join(re.escape(k) for k in keys) + r')\b') if keys else None
|
||||
new_body = pat.sub(lambda m: renames[m.group(1)], body) if pat else body
|
||||
new_body = re.sub(rf'\b{re.escape(seed_name)}\b', member_name, new_body, flags=re.I)
|
||||
if member_name not in new_body:
|
||||
return None, "fn-name rename produced no definition"
|
||||
# R32: assert the DEFINITION, not merely the name. The seed name used to be recovered by
|
||||
# scanning the body for the first `func_XXXXXXXX(` token — which in a de-macroized body is the
|
||||
# first EXTERN DECLARATION, not the definition. That renamed a callee to the member's name and
|
||||
# left the definition under the seed's, and the draft linked with `undefined reference to
|
||||
# <member>`: a full binary build spent to learn what one regex knows.
|
||||
if not re.search(rf'^[^\n=;]*\b{re.escape(member_name)}\s*\([^;]*\)\s*\{{', new_body, re.M):
|
||||
return None, "no definition of the member after rename (seed-name misidentified?)"
|
||||
|
||||
if already_self_contained:
|
||||
# A de-macroized body already carries its own externs; synthesizing a second set would
|
||||
@@ -200,10 +210,9 @@ def main():
|
||||
skip["symbol_map: " + err.split("(")[0].strip()] += 1
|
||||
continue
|
||||
dest = "".join(open(p).read() for p in sorted(glob.glob(f"src/{m['binary']}/*.c")))
|
||||
body_name = re.search(r'\b(func_[0-9A-Fa-f]{8})\s*\(', body)
|
||||
draft, why = build_draft(body, body_name.group(1) if body_name else seed["name"],
|
||||
draft, why = build_draft(body, sb.get("name") or seed["name"],
|
||||
m["name"], ren, seed_text, dest,
|
||||
already_self_contained=(kind == "macro"))
|
||||
already_self_contained=False)
|
||||
if draft is None:
|
||||
skip[why.split(" for ")[0]] += 1
|
||||
continue
|
||||
|
||||
+19
-4
@@ -47,16 +47,31 @@ def draft_syms(path):
|
||||
|
||||
|
||||
def body_text(path, name):
|
||||
"""The C body of ONE function out of a file (a draft is one function; a seed's file is not).
|
||||
"""The C body of ONE function out of a FILE (thin wrapper over body_in_text)."""
|
||||
if not path or not os.path.isfile(path):
|
||||
return None
|
||||
return body_in_text(open(path).read(), name)
|
||||
|
||||
|
||||
def body_in_text(txt, name):
|
||||
"""The C body of ONE function out of a block of text (a draft is one function; a seed's file,
|
||||
or a de-macroized DEFINE_ block, is not).
|
||||
|
||||
Matches an `DEFINE_<name>(` engine_core macro or a plain definition, then brace-matches. A
|
||||
whole-file scan would drag every OTHER function's symbols into the comparison and drown the
|
||||
signal, so callers annotating a SEED must pass through here."""
|
||||
if not path or not os.path.isfile(path):
|
||||
return None
|
||||
txt = _strip_comments(open(path).read())
|
||||
txt = _strip_comments(txt)
|
||||
# Column-0 first (a file's definitions start there). The INDENTED fallback exists for a
|
||||
# de-macroized DEFINE_ block, whose lines are all indented — but an indented `func_X(` is far
|
||||
# more often a CALL, so that form must be confirmed by a `{` with no `;` before it.
|
||||
m = (re.search(rf'\bDEFINE_{re.escape(name)}\s*\(', txt) or
|
||||
re.search(rf'^[A-Za-z_][^\n=;]*\b{re.escape(name)}\s*\(', txt, re.M))
|
||||
if not m:
|
||||
for cand in re.finditer(rf'^[ \t]*[A-Za-z_][^\n=;]*\b{re.escape(name)}\s*\(', txt, re.M):
|
||||
i = txt.find('{', cand.start())
|
||||
if i >= 0 and ';' not in txt[cand.end():i]:
|
||||
m = cand
|
||||
break
|
||||
if not m:
|
||||
return None
|
||||
i = txt.find('{', m.start())
|
||||
|
||||
Reference in New Issue
Block a user