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:
Drew T
2026-08-14 05:28:40 -06:00
parent 4fde6e3e81
commit 9a3e438ed9
2 changed files with 36 additions and 12 deletions
+17 -8
View File
@@ -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
View File
@@ -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())