mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-10-04 16:28:11 -04:00
T4.c1: restruct: exact --only main selector (top-level src/*.c, src/resident/); kit-corpus
This commit is contained in:
@@ -8,10 +8,12 @@ write the per-offset decision record. --header / --decisions rewrite only the te
|
||||
created with its hand-written skeleton. Flat structs only (the rewriter spells flat members; no unions, no nesting).
|
||||
|
||||
.venv/bin/python tools/head_types.py [--type NAME ...] [--map PATH] [--header PATH] [--decisions PATH] [--selftest]
|
||||
.venv/bin/python tools/head_types.py --header PATH --add-pads CANDIDATES.json # additive: pad splits only (P39 T3.c6)
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import pathlib
|
||||
import re
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))
|
||||
@@ -186,6 +188,77 @@ def splice(path, skeleton, begin, end, body):
|
||||
return True
|
||||
|
||||
|
||||
PAD_RE = re.compile(r"^(\s*)/\* (0x[0-9a-f]+) \*/ u8 pad[0-9A-F]+\[(0x[0-9a-f]+)\];$")
|
||||
|
||||
|
||||
def add_pads(region, cands):
|
||||
"""Additive mode (P39 T3.c6): split pad arrays of the region's typedefs to insert candidate fields; every other line
|
||||
is kept byte-for-byte. cands = [{type, off, width, sign}]. -> (new region, [added], [(cand, why skipped)])."""
|
||||
want = {}
|
||||
for c in cands:
|
||||
want.setdefault(c["type"], []).append(c)
|
||||
out, added, skipped, block = [], [], [], []
|
||||
for line in region.split("\n"):
|
||||
block.append(line)
|
||||
m = re.match(r"^\} (\w+);", line)
|
||||
if not m:
|
||||
continue
|
||||
cs, used = want.pop(m.group(1), []), set()
|
||||
for k, bl in enumerate(block):
|
||||
pm = PAD_RE.match(bl)
|
||||
if not pm:
|
||||
out.append(bl)
|
||||
continue
|
||||
ind, p0, pn = pm.group(1), int(pm.group(2), 16), int(pm.group(3), 16)
|
||||
ins = sorted((c for c in cs if p0 <= c["off"] and c["off"] + c["width"] <= p0 + pn), key=lambda c: c["off"])
|
||||
cur, new = p0, []
|
||||
for c in ins:
|
||||
off, w, sg = c["off"], c["width"], c["sign"]
|
||||
if off < cur or (w, sg) not in sl.TYPE_OF or off % min(w, 4):
|
||||
skipped.append((c, "overlap" if off < cur else "width/sign/align"))
|
||||
continue
|
||||
if off > cur:
|
||||
new.append(f"{ind}/* {cur:#06x} */ u8 pad{cur:X}[{off - cur:#x}];")
|
||||
new.append(f"{ind}/* {off:#06x} */ {sl.TYPE_OF[(w, sg)]} unk{off:X};")
|
||||
added.append(c)
|
||||
used.add(id(c))
|
||||
cur = off + w
|
||||
if cur == p0:
|
||||
out.append(bl)
|
||||
continue
|
||||
if cur < p0 + pn:
|
||||
new.append(f"{ind}/* {cur:#06x} */ u8 pad{cur:X}[{p0 + pn - cur:#x}];")
|
||||
out.extend(new)
|
||||
skipped += [(c, "not in a pad") for c in cs if id(c) not in used and not any(c is s[0] for s in skipped)]
|
||||
block = []
|
||||
out.extend(block)
|
||||
skipped += [(c, "type not in region") for cs in want.values() for c in cs]
|
||||
return "\n".join(out), added, skipped
|
||||
|
||||
|
||||
def region_of(path):
|
||||
p = REPO / path if not pathlib.Path(path).is_absolute() else pathlib.Path(path)
|
||||
old = p.read_text()
|
||||
i, j = old.find(C_BEGIN), old.find(C_END)
|
||||
if i < 0 or j < i:
|
||||
sys.exit(f"head_types: {path} lacks the markers {C_BEGIN} … {C_END}")
|
||||
return old[i + len(C_BEGIN) + 1:j]
|
||||
|
||||
|
||||
def selftest_add_pads():
|
||||
reg = ("typedef struct {\n /* c */\n /* 0x0000 */ u16 unk0;\n /* 0x0002 */ u8 pad2[0xa];\n"
|
||||
" /* 0x000c */ s32 unkC;\n} Unkstruct_T; /* size = 0x10 */\n")
|
||||
cands = [dict(type="Unkstruct_T", off=4, width=4, sign="s"), dict(type="Unkstruct_T", off=9, width=1, sign="u"),
|
||||
dict(type="Unkstruct_T", off=0, width=1, sign="u"), dict(type="Unkstruct_T", off=6, width=2, sign="u")]
|
||||
new, added, skipped = add_pads(reg, cands)
|
||||
want = ("typedef struct {\n /* c */\n /* 0x0000 */ u16 unk0;\n /* 0x0002 */ u8 pad2[0x2];\n"
|
||||
" /* 0x0004 */ s32 unk4;\n /* 0x0008 */ u8 pad8[0x1];\n /* 0x0009 */ u8 unk9;\n"
|
||||
" /* 0x000a */ u8 padA[0x2];\n /* 0x000c */ s32 unkC;\n} Unkstruct_T; /* size = 0x10 */\n")
|
||||
ok = new == want and len(added) == 2 and sorted(s[1] for s in skipped) == ["not in a pad", "overlap"]
|
||||
print("add_pads OK" if ok else f"add_pads FAIL\n{new}\n{skipped}")
|
||||
return ok
|
||||
|
||||
|
||||
def selftest():
|
||||
rec = dict(name="Unkstruct_TEST", sites=10, bodies=2, size_hint=10, evidence={}, globals_at=[],
|
||||
layout=[[0, 4, "s", 5], [2, 2, "u", 4], [4, 2, "s", 3], [6, 2, "u", 3], [7, 1, "u", 9], [8, 4, "s", 2]],
|
||||
@@ -195,6 +268,7 @@ def selftest():
|
||||
# best: 0:2u3 + 2:2u4 + 4:2s3 + 7:1u9 + 8:4s2 = 21 vs 0:4s5 + 4:2s3 + 7:1u9 + 8:4s2 = 19
|
||||
want = "fields=5 kept_sites=21 dropped_sites=10 conflict_offsets=4 overlaps=4 misaligned=2 union_candidates=0"
|
||||
ok = summary == want and "/* 0x0007 */ u8 unk7;" in txt and "/* 0x0006 */ u8 pad6[0x1];" in txt and "size = 0xc" in txt
|
||||
ok = selftest_add_pads() and ok
|
||||
print("OK" if ok else f"FAIL\n{summary}\n{txt}\n" + "\n".join(lines))
|
||||
return 0 if ok else 1
|
||||
|
||||
@@ -205,10 +279,24 @@ def main():
|
||||
ap.add_argument("--map", default=".run/P37/census/struct_map.json")
|
||||
ap.add_argument("--header", help="C header to rewrite between /* head_types:begin */ … /* head_types:end */")
|
||||
ap.add_argument("--decisions", help="markdown to rewrite between <!-- head_types:begin --> … <!-- head_types:end -->")
|
||||
ap.add_argument("--add-pads", metavar="CANDIDATES.json",
|
||||
help="additive mode: keep the --header region's fields byte-for-byte, only split pad arrays to insert "
|
||||
"the candidate fields [{type, off, width, sign}] (no struct map read)")
|
||||
ap.add_argument("--selftest", action="store_true")
|
||||
a = ap.parse_args()
|
||||
a =ap.parse_args()
|
||||
if a.selftest:
|
||||
return selftest()
|
||||
if a.add_pads:
|
||||
if not a.header:
|
||||
sys.exit("head_types: --add-pads needs --header")
|
||||
new, added, skipped = add_pads(region_of(a.header), json.loads((REPO / a.add_pads).read_text()))
|
||||
for c in added:
|
||||
print(f"added {c['type']} 0x{c['off']:X} {c['width']}{c['sign']}")
|
||||
for c, why in skipped:
|
||||
print(f"skipped {c['type']} 0x{c['off']:X} {c['width']}{c['sign']}: {why}")
|
||||
ch = splice(a.header, C_SKELETON, C_BEGIN, C_END, new)
|
||||
print(f"head_types: {a.header} {'written' if ch else 'unchanged'} (add-pads: {len(added)} added, {len(skipped)} skipped)")
|
||||
return 0
|
||||
smap = json.loads((REPO / a.map).read_text())
|
||||
by = {t["name"]: t for t in smap["types"]}
|
||||
chunks, md = [], []
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
tools/restruct.py --status --rung L --only <CanonType> … # the HEAD-FOLD rows (latest per unit): verdict -> cause -> units
|
||||
(rung L with `--only <CanonType> …` = HEAD-FOLD mode, P38 T4: same-size other-layout definitions -> `typedef <Canon> <Local>;` + renames)
|
||||
tools/restruct.py --check-ledger # every MEMBER site still a leaf of its (current) canonical type
|
||||
tools/restruct.py --rung S --residue --label <lbl> [--only X …] # T3.c7: a no-edit RESIDUAL(<cause>) row per undrawn body (ledger only)
|
||||
tools/restruct.py --check-residue [--only X …] # P/X census sites (default ov_*) with no latest rung-S row; exit 1 if any
|
||||
tools/restruct.py --audit-types # the naming invariant over the canonical type files (struct_layout.audit_definition)
|
||||
tools/restruct.py --write-types --top N | --type NAME … [--space ov] # the WRITER: canonical definitions rendered from the struct map (a preview)
|
||||
tools/restruct.py --selftest [--real] # the fixture (a stub oracle); --real adds the calibration + known-true controls
|
||||
@@ -1186,6 +1188,20 @@ def s_prior_rows(tu, fn):
|
||||
|
||||
S_NO_EDIT_VERDICTS = ("NO-SITE", "TYPE-NOT-VISIBLE", "TYPE-NOT-CANONICAL")
|
||||
|
||||
_NB_DEREF = re.compile(r"\*\s*\(\s*[A-Za-z_][\w\s]*\*+\s*\)\s*\(\s*([^()]*?)\s*\+\s*0x([0-9A-Fa-f]+)\s*\)")
|
||||
_NB_MEMBER = re.compile(r"\(\s*\(\s*[A-Za-z_]\w*\s*\*\s*\)\s*([^()]*?)\s*\)\s*->\s*unk([0-9A-Fa-f]+)")
|
||||
|
||||
|
||||
def s_base_norm(key):
|
||||
"""T3.c8: a site base key with its offset accesses spelling-normalised — `*(T *)(X + 0xNN)` and `((S *)X)->unkNN` both become
|
||||
`@[X+NN]` (innermost first) — so a KEPT entry keyed on the pre-respell text still matches the base the census spells after a
|
||||
respell of a nested base inside the macro (t3_S28 func_8017EA20: `*(s32 *)(param_1 + 0x64)` → `((Unkstruct_800B5CB8 *)param_1)->unk64`)."""
|
||||
hx = lambda m: f"@[{m.group(1).strip()}+{int(m.group(2), 16):x}]"
|
||||
prev = None
|
||||
while prev != key:
|
||||
prev, key = key, _NB_MEMBER.sub(hx, _NB_DEREF.sub(hx, key))
|
||||
return re.sub(r"\s+", "", key)
|
||||
|
||||
|
||||
def s_macro_rows(tu, text, rows):
|
||||
"""T3.c4: every kept-cast macro site (census form K) of a drawn body in the FINAL text gets its own KEPT(<class>) row in the
|
||||
@@ -1208,8 +1224,10 @@ def s_macro_rows(tu, text, rows):
|
||||
n = 0
|
||||
for s in ks:
|
||||
want, key = f"KEPT({s['cls']})", f"{s['bclass']}:{s['base']}"
|
||||
nkey = s_base_norm(key) # T3.c8: a respelled nested base still matches (was: exact base or line)
|
||||
src = next((lb for lb, e in cands if e.get("verdict") == want and (e.get("line") == s["line"] if s["cls"] == "REINTERP" else
|
||||
e.get("off") == s["off"] and (e.get("base") == key or e.get("line") == s["line"]))), None)
|
||||
e.get("off") == s["off"] and (e.get("base") == key or e.get("line") == s["line"]
|
||||
or s_base_norm(e.get("base") or "") == nkey))), None)
|
||||
if src is None:
|
||||
continue
|
||||
e_ = site_row(s, want, tname=next((b.get("type") for b in row.get("bases") or [] if b.get("base") == key), None), cause=s["cls"])
|
||||
@@ -3351,11 +3369,13 @@ def matches_only(only, tu, unit, extra=()):
|
||||
hay = [tu, unit] + list(extra)
|
||||
if tu and unit and f"{tu}:{unit}" in only: # T3.c4: `TU:FN` names one body (overlay fn names repeat)
|
||||
return True
|
||||
if "main" in only and tu and (tu.count("/") == 1 or tu.startswith("src/resident/")): # P39 T4: main-executable TUs (as :1718/:4137)
|
||||
return True
|
||||
globs =[o for o in only if any(c in o for c in "*?[")] # P39 T3: `ov_*` against the alias / src dir (and the path)
|
||||
if globs and tu and any(fnmatch.fnmatchcase(tu, o) or (tu.startswith("src/") and fnmatch.fnmatchcase(tu.split("/")[1], o)) for o in globs):
|
||||
return True
|
||||
return any(o == h or h.endswith("/" + o) or h.startswith("src/" + o + "/") or ("/" + o + "/") in h
|
||||
or (o in h and o.startswith(("Unkstruct_", "func_", "D_"))) for o in only for h in hay if h)
|
||||
or (o in h and o.startswith(("Unkstruct_", "func_", "D_"))) for o in only if o != "main" for h in hay if h) # exact: not src/shared/main/
|
||||
|
||||
|
||||
def s_finished(tu, fns, latest, done=frozenset()):
|
||||
@@ -3411,6 +3431,101 @@ def plan_S(headers, batch, only, done, latest=None, data=None):
|
||||
return [(tu, files[tu]) for tu in ordered[:batch]], len(ordered), skipped
|
||||
|
||||
|
||||
# T3.c7: the residue — plan_S skips every body none of whose bases is a visible canonical type (34131 ov bodies had no rung-S row).
|
||||
# `--rung S --residue` ledgers one no-edit row per such body (no source touched, no compile); `--check-residue` counts what is left.
|
||||
RESIDUE_FILE_UNIT = "<file-scope>" # the unit of census sites outside any function (site fn null)
|
||||
RESIDUE_DEFAULT_ONLY = ["ov_*"]
|
||||
|
||||
|
||||
def residue_cause(s, can=None):
|
||||
"""Why an undrawn P/X census site stays raw: FILE-SCOPE (fn null), NO-TYPE-IN-MAP (untyped), TYPE-NOT-CANONICAL, TYPE-NOT-VISIBLE,
|
||||
else site_class's cause for a typed base (MISALIGNED / NO-EDIT: a visible canonical type plan_S's bbt did not pair with the base)."""
|
||||
can = canon_types() if can is None else can
|
||||
t = s.get("type")
|
||||
if not s.get("fn"):
|
||||
return "FILE-SCOPE"
|
||||
if not t:
|
||||
return "NO-TYPE-IN-MAP"
|
||||
if t not in can:
|
||||
return "TYPE-NOT-CANONICAL"
|
||||
if not type_visible(s["tu"], t):
|
||||
return "TYPE-NOT-VISIBLE"
|
||||
return site_class(s, None, typed=True)
|
||||
|
||||
|
||||
def residue_sites(only, latest, path=None):
|
||||
"""{(tu, unit): [census P/X site]} over the TUs matching `only` (default ov_*) — the bodies with no latest rung-S row (R100)."""
|
||||
only = only or RESIDUE_DEFAULT_ONLY
|
||||
out = collections.defaultdict(list)
|
||||
with open(path or CENSUS / "sites.jsonl") as f:
|
||||
for l in f:
|
||||
if not l.startswith(('{"form": "P"', '{"form": "X"')):
|
||||
continue
|
||||
s = json.loads(l)
|
||||
unit = s.get("fn") or RESIDUE_FILE_UNIT
|
||||
if ("S", s["tu"], unit) in latest or not matches_only(only, s["tu"], unit):
|
||||
continue
|
||||
out[(s["tu"], unit)].append(s)
|
||||
return out
|
||||
|
||||
|
||||
def residue_rows(groups, label, calib_id):
|
||||
"""One rung-S no-edit row per (tu, unit) of residue_sites, sorted (tu, unit): nhash_before == nhash_after == the body's current nhash
|
||||
(the masked file's for RESIDUE_FILE_UNIT; None when the body is gone); every site RESIDUAL(<cause>); verdict the shared cause when it
|
||||
is TYPE-NOT-CANONICAL / TYPE-NOT-VISIBLE (S_NO_EDIT_VERDICTS), else RESIDUAL. Bases carry the cause as status: never s_judged."""
|
||||
can = canon_types()
|
||||
by_tu = collections.defaultdict(list)
|
||||
for tu, unit in groups:
|
||||
by_tu[tu].append(unit)
|
||||
rows = []
|
||||
for tu in sorted(by_tu):
|
||||
path = REPO / tu
|
||||
nh, file_nh = {}, None
|
||||
if path.exists():
|
||||
text = path.read_text(errors="surrogateescape")
|
||||
masked = dl.same_len_mask(text)
|
||||
for d in sc.scan_text(text, tu, shared_defs=None):
|
||||
if d["form"] == "def":
|
||||
nh.setdefault(d["name"], body_nhash(masked, d))
|
||||
file_nh = lc.norm_hash(masked)
|
||||
for unit in sorted(by_tu[tu]):
|
||||
sites = []
|
||||
for s in sorted(groups[(tu, unit)], key=lambda x: (x.get("line") or 0, x.get("pos") or 0)):
|
||||
c = residue_cause(s, can)
|
||||
sites.append(dict(form=s.get("form"), line=s.get("line"), base=f"{s.get('bclass')}:{s.get('base')}", off=s.get("off"),
|
||||
type=s.get("type"), verdict=f"RESIDUAL({c})", cause=c))
|
||||
causes = {x["cause"] for x in sites}
|
||||
verdict = next(iter(causes)) if len(causes) == 1 and causes <= set(S_NO_EDIT_VERDICTS) else "RESIDUAL"
|
||||
bases = {}
|
||||
for x in sites:
|
||||
bases.setdefault(x["base"], dict(base=x["base"], type=x["type"], status=x["cause"]))
|
||||
fs = unit == RESIDUE_FILE_UNIT
|
||||
h = file_nh if fs else nh.get(unit)
|
||||
rows.append(dict(ts=time.strftime("%Y-%m-%d %H:%M:%S"), label=label, rung="S", calib=calib_id, tu=tu, unit=unit,
|
||||
fn=None if fs else unit, addr=None if fs else dl.fn_addr(unit, tu), header=tu.endswith(".h"),
|
||||
nhash_before=h, nhash_after=h, bases=list(bases.values()), skips={}, sites=sites, verdict=verdict))
|
||||
return rows
|
||||
|
||||
|
||||
def residue(a):
|
||||
"""--rung S --residue: append the residue rows (ledger only) and print the per-cause site counts."""
|
||||
calib = json.loads(oracle.CALIB.read_text()) if oracle.CALIB.exists() else {}
|
||||
rows = residue_rows(residue_sites(a.only, ledger_latest(load_ledger())), a.label, dict(head=calib.get("head"), stamp=calib.get("stamp")))
|
||||
ledger_append(rows)
|
||||
cause = collections.Counter(x["cause"] for r in rows for x in r["sites"])
|
||||
verd = collections.Counter(r["verdict"] for r in rows)
|
||||
print(f"restruct --residue {a.label}: {len(rows)} rows · {sum(cause.values())} sites · causes {dict(sorted(cause.items()))} · verdicts {dict(sorted(verd.items()))}")
|
||||
return 0
|
||||
|
||||
|
||||
def check_residue(a):
|
||||
"""--check-residue: the P/X census sites (TUs matching --only, default ov_*) whose body has no latest rung-S row; exit 1 if any."""
|
||||
g = residue_sites(a.only, ledger_latest(load_ledger()))
|
||||
n = sum(len(v) for v in g.values())
|
||||
print(f"uncovered {n} sites / {len(g)} bodies")
|
||||
return 1 if n else 0
|
||||
|
||||
|
||||
def argcheck_rows():
|
||||
"""argcheck's lying-declaration rows [{tu, callee, …}] (every callee spelled other than its definition), cached on the src stamp."""
|
||||
import argcheck
|
||||
@@ -4707,12 +4822,27 @@ def selftest(real=False):
|
||||
line5 = tc.check_casts_verdict({}, mk5, {("S", tu3, "fx_e"): row5}, [], set())[1]
|
||||
ok("re-judge carries the earlier KEPT rows in the census key: KEPT, unbacked 0", row5["verdict"] == "KEPT" and "(unbacked 0)" in line5
|
||||
and {e["macro_of"] for e in row5["sites"] if e.get("macro_of")} == {"t3_S1"}, f"{row5['verdict']} {line5}")
|
||||
# T3.c8: func_8017EA20 shape — the earlier KEPT(NON-STRUCT) row keyed the pre-respell nested base on another line
|
||||
txt6 = FIXTURE_TU_S3.replace("void func_8FF00000_impl(s32 param_1) __asm__", "void fx_f(s32 a0) {\n s32 v;\n"
|
||||
" v = CAST_NONSTRUCT(u16, *(s32 *)(((Unkstruct_TEST *)a0)->unk4 + 0x20), 0x12);\n}\n\n"
|
||||
"void func_8FF00000_impl(s32 param_1) __asm__", 1)
|
||||
mk6 = [s_ for s_ in file_view(tu3, txt6)[4] if s_["form"] == "K" and s_.get("fn") == "fx_f"]
|
||||
S_PRIOR = {(tu3, "fx_f"): [dict(rung="S", tu=tu3, unit="fx_f", label="t3_S13", verdict="KEPT", sites=[
|
||||
dict(base="nested:*(s32 *)(*(s32 *)(a0 + 0x4) + 0x20)", off=0x12, line=999, verdict="KEPT(NON-STRUCT)")])]}
|
||||
row6 = dict(fn="fx_f", verdict="MEMBERS", sites=[], bases=[])
|
||||
s_macro_rows(tu3, txt6, [row6])
|
||||
line6 = tc.check_casts_verdict({}, mk6, {("S", tu3, "fx_f"): row6}, [], set())[1]
|
||||
ok("carry: a nested base respelled under a kept macro still matches its earlier KEPT row (unbacked 0)", len(mk6) == 1
|
||||
and "(unbacked 0)" in line6 and [e.get("macro_of") for e in row6["sites"]] == ["t3_S13"], f"{[(s_['bclass'], s_['base']) for s_ in mk6]} {line6}")
|
||||
S_PRIOR = {}
|
||||
ok("respell_edit: a lower-precedence p or a non-literal k stays raw", respell_edit("*(s32 *)(a << 2 + 4)", "*(s32 *)(a << 2 + 4)",
|
||||
dict(form="P", pos=0, end=20, base=None, off=4), "SIGN") is None and respell_edit("*(s32 *)(a + i)", "*(s32 *)(a + i)", dict(form="P", pos=0, end=15, base="a", off=0), "SIGN") is None)
|
||||
ok("--only glob: ov_* matches the src dir, not main", matches_only(["ov_*"], "src/ov_SC05_010/x.c", "f") and not matches_only(["ov_*"], "src/main/x.c", "f")
|
||||
and matches_only(["src/ov_SC05_010/x.c"], "src/ov_SC05_010/x.c", "f")
|
||||
and matches_only(["src/a/x.c:func_1"], "src/a/x.c", "func_1") and not matches_only(["src/a/x.c:func_1"], "src/b/y.c", "func_1"))
|
||||
ok("--only main: top-level src/*.c and src/resident/, not md_/ov_", matches_only(["main"], "src/800.c", "f") and matches_only(["main"], "src/resident/x.c", "f")
|
||||
and not matches_only(["main"], "src/md_A/x.c", "f") and not matches_only(["main"], "src/ov_A/x.c", "f")
|
||||
and not matches_only(["main"], "src/shared/main/x.h", "f"))
|
||||
# ---- rung D on the fixture
|
||||
DEFS_OVERRIDE = {"fx_callee": [("src/ov/fx_defs.c", "void", "s32 a0, s32 a1", False)], "fx_kr": [("src/ov/fx_defs.c", "s32", "s32 a, s32 b", True)]}
|
||||
units = d_units(tu, text)
|
||||
@@ -4915,6 +5045,25 @@ def selftest(real=False):
|
||||
_map_cache = map_keep_
|
||||
ok("apply: the planned pinned base is CANONICAL with edits though the struct map lacks the name",
|
||||
[(b_[0], b_[1], b_[2]) for b_ in pb_] == [("param:a0", "Unkstruct_TEST", "CANONICAL")] and len(pb_[0][3]) > 0, f"{[b_[:3] for b_ in pb_]}")
|
||||
# T3.c7: --residue rows (ledger only) and --check-residue over a fixture sites.jsonl
|
||||
rs_p = dirp / "residue_sites.jsonl"
|
||||
rs_p.write_text("".join(json.dumps(x) + "\n" for x in [
|
||||
dict(form="P", tu=tu, fn="fx_a", line=9, base="a0", bclass="param", off=0, type="Unkstruct_TEST"),
|
||||
dict(form="X", tu=tu, fn="fx_b", line=20, base="a0", bclass="param", off=4, type=None),
|
||||
dict(form="P", tu=tu, fn="fx_z", line=30, base="D_1", bclass="global", off=0, type="Unkstruct_GONE"),
|
||||
dict(form="P", tu=tu, fn=None, line=2, base="D_2", bclass="gaddr", off=0, type=None),
|
||||
dict(form="K", tu=tu, fn="fx_c", line=40, base="a0", bclass="param", off=0, type=None)]))
|
||||
lat_ = ledger_latest([dict(rung="S", tu=tu, unit="fx_a", verdict="MEMBERS", nhash_after=nh_, bases=b_can_)])
|
||||
rr_ = residue_rows(residue_sites([tu], lat_, rs_p), "rtest", {})
|
||||
db_ = next(x for x in defs_ if x["name"] == "fx_b")
|
||||
ok("--residue: one no-edit row per undrawn body (sorted), causes FILE-SCOPE/NO-TYPE-IN-MAP/TYPE-NOT-CANONICAL, never judged",
|
||||
[(r["unit"], r["verdict"], r["sites"][0]["verdict"]) for r in rr_] == [("<file-scope>", "RESIDUAL", "RESIDUAL(FILE-SCOPE)"),
|
||||
("fx_b", "RESIDUAL", "RESIDUAL(NO-TYPE-IN-MAP)"), ("fx_z", "TYPE-NOT-CANONICAL", "RESIDUAL(TYPE-NOT-CANONICAL)")]
|
||||
and rr_[1]["nhash_before"] == rr_[1]["nhash_after"] == body_nhash(m_, db_) and not any(s_judged(r) for r in rr_)
|
||||
and ledger_index(rr_)[0] == set(), f"{[(r['unit'], r['verdict'], r['nhash_after']) for r in rr_]}")
|
||||
ok("--check-residue: the residue rows cover every P/X site; without them 3 bodies / 3 sites stay uncovered",
|
||||
residue_sites([tu], ledger_latest(list(lat_.values()) + rr_), rs_p) == {}
|
||||
and sum(len(v) for v in residue_sites([tu], lat_, rs_p).values()) == 3)
|
||||
ok("the selftest never touches the live inflight.json", True)
|
||||
ok("struct_layout selftest", sl.selftest() == 0)
|
||||
JUDGE_STUB = None
|
||||
@@ -4967,6 +5116,8 @@ def main():
|
||||
ap.add_argument("--restore", action="store_true")
|
||||
ap.add_argument("--status", action="store_true")
|
||||
ap.add_argument("--check-ledger", action="store_true")
|
||||
ap.add_argument("--residue", action="store_true", help="rung S: one no-edit row per undrawn body with P/X census sites (ledger only; --only, default ov_*)")
|
||||
ap.add_argument("--check-residue", action="store_true", help="P/X census sites (--only, default ov_*) whose body has no latest rung-S row; exit 1 if any")
|
||||
ap.add_argument("--audit-types", action="store_true")
|
||||
ap.add_argument("--write-types", action="store_true")
|
||||
ap.add_argument("--type", nargs="*")
|
||||
@@ -4998,6 +5149,12 @@ def main():
|
||||
sys.exit(status())
|
||||
if a.check_ledger:
|
||||
sys.exit(check_ledger(a))
|
||||
if a.residue:
|
||||
if a.rung != "S":
|
||||
sys.exit("restruct --residue is rung S only")
|
||||
sys.exit(residue(a))
|
||||
if a.check_residue:
|
||||
sys.exit(check_residue(a))
|
||||
if a.audit_types:
|
||||
sys.exit(audit_types(a))
|
||||
if a.write_types:
|
||||
|
||||
+6
-1
@@ -3369,11 +3369,13 @@ def matches_only(only, tu, unit, extra=()):
|
||||
hay = [tu, unit] + list(extra)
|
||||
if tu and unit and f"{tu}:{unit}" in only: # T3.c4: `TU:FN` names one body (overlay fn names repeat)
|
||||
return True
|
||||
if "main" in only and tu and (tu.count("/") == 1 or tu.startswith("src/resident/")): # P39 T4: main-executable TUs (as :1718/:4137)
|
||||
return True
|
||||
globs =[o for o in only if any(c in o for c in "*?[")] # P39 T3: `ov_*` against the alias / src dir (and the path)
|
||||
if globs and tu and any(fnmatch.fnmatchcase(tu, o) or (tu.startswith("src/") and fnmatch.fnmatchcase(tu.split("/")[1], o)) for o in globs):
|
||||
return True
|
||||
return any(o == h or h.endswith("/" + o) or h.startswith("src/" + o + "/") or ("/" + o + "/") in h
|
||||
or (o in h and o.startswith(("Unkstruct_", "func_", "D_"))) for o in only for h in hay if h)
|
||||
or (o in h and o.startswith(("Unkstruct_", "func_", "D_"))) for o in only if o != "main" for h in hay if h) # exact: not src/shared/main/
|
||||
|
||||
|
||||
def s_finished(tu, fns, latest, done=frozenset()):
|
||||
@@ -4838,6 +4840,9 @@ def selftest(real=False):
|
||||
ok("--only glob: ov_* matches the src dir, not main", matches_only(["ov_*"], "src/ov_SC05_010/x.c", "f") and not matches_only(["ov_*"], "src/main/x.c", "f")
|
||||
and matches_only(["src/ov_SC05_010/x.c"], "src/ov_SC05_010/x.c", "f")
|
||||
and matches_only(["src/a/x.c:func_1"], "src/a/x.c", "func_1") and not matches_only(["src/a/x.c:func_1"], "src/b/y.c", "func_1"))
|
||||
ok("--only main: top-level src/*.c and src/resident/, not md_/ov_", matches_only(["main"], "src/800.c", "f") and matches_only(["main"], "src/resident/x.c", "f")
|
||||
and not matches_only(["main"], "src/md_A/x.c", "f") and not matches_only(["main"], "src/ov_A/x.c", "f")
|
||||
and not matches_only(["main"], "src/shared/main/x.h", "f"))
|
||||
# ---- rung D on the fixture
|
||||
DEFS_OVERRIDE = {"fx_callee": [("src/ov/fx_defs.c", "void", "s32 a0, s32 a1", False)], "fx_kr": [("src/ov/fx_defs.c", "s32", "s32 a, s32 b", True)]}
|
||||
units = d_units(tu, text)
|
||||
|
||||
Reference in New Issue
Block a user